aboutsummaryrefslogtreecommitdiffstats
path: root/schedcat
diff options
context:
space:
mode:
authorJeremy Erickson <jerickso@cs.unc.edu>2012-09-20 20:41:44 -0400
committerJeremy Erickson <jerickso@cs.unc.edu>2012-09-25 12:39:56 -0400
commit37dbd04e4f9d8956cf4be1c196e282760aa37011 (patch)
treed2d2994c5dd697ec493555b22bbce999e1bfe4ba /schedcat
parent8ba27be920e6bd47442e33946783a38c0fa37356 (diff)
Initial GEL Piecewise Linear implementation
Diffstat (limited to 'schedcat')
-rw-r--r--schedcat/sched/edf/gel_pl.py234
1 files changed, 234 insertions, 0 deletions
diff --git a/schedcat/sched/edf/gel_pl.py b/schedcat/sched/edf/gel_pl.py
new file mode 100644
index 0000000..ef046e1
--- /dev/null
+++ b/schedcat/sched/edf/gel_pl.py
@@ -0,0 +1,234 @@
1"""This module computes tardiness bounds for G-EDF-like schedulers. Currently
2 G-EDF and G-FL (See Erickson and Anderson ECRTS'12) are supported. This
3 module works by analyzing compliant vectors (see Erickson, Devi, Baruah
4 ECRTS'10), analyzing the involved piecewise linear functions (see Erickson,
5 Guan, Baruah OPODIS'10). Notation is used as in EA'12.
6
7 The analysis is general enough to support other GEL schedulers trivially.
8"""
9
10from __future__ import division
11
12from math import ceil
13
14from schedcat.util.quantor import forall
15
16import schedcat.sched
17if schedcat.sched.using_native:
18 import schedcat.sched.native as native
19
20import heapq
21
22class AnalysisDetails:
23 def __init__(self, num_tasks, gel_obj = None):
24 if gel_obj is not None:
25 self.bounds = [gel_obj.get_bound(i) for i in range(num_tasks)]
26 self.S_i = [gel_obj.get_Si(i) for i in range(num_tasks)]
27 self.G_i = [gel_obj.get_Gi(i) for i in range(num_tasks)]
28
29def compute_gfl_response_details(no_cpus, tasks, rounds):
30 """This function computes response time bounds for the given set of tasks
31 using G-FL. "rounds" determines the number of rounds of binary search;
32 using "0" results in using the exact algorithm.
33 """
34 if schedcat.sched.using_native:
35 native_ts = schedcat.sched.get_native_taskset(tasks)
36 gel_obj = native.GELPl(native.GELPl.GFL,
37 no_cpus, native_ts, rounds)
38 return AnalysisDetails(len(tasks), gel_obj)
39 else:
40 gfl_pps = [task.deadline - int((no_cpus - 1) / (no_cpus) * task.cost)
41 for task in tasks]
42 return compute_response_bounds(no_cpus, tasks, gfl_pps, rounds)
43
44def compute_gedf_response_details(no_cpus, tasks, rounds):
45 """This function computes response time bounds for a given set of tasks
46 using G-EDF. "rounds" determines the number of rounds of binary search;
47 using "0" results in using the exact algorithm.
48 """
49 if schedcat.sched.using_native:
50 native_ts = schedcat.sched.get_native_taskset(tasks)
51 gel_obj = native.GELPl(native.GELPl.GEDF,
52 no_cpus, native_ts, rounds)
53 return AnalysisDetails(len(tasks), gel_obj)
54 else:
55 gedf_pps = [task.deadline for task in tasks]
56 return compute_response_bounds(no_cpus, tasks, gedf_pps, rounds)
57
58def compute_response_bounds(no_cpus, tasks, sched_pps, rounds):
59 """This function computes response time bounds for the given set of tasks
60 and priority points. "rounds" determines the number of rounds of binary
61 search; using "0" results in using the exact algorithm.
62 """
63
64 if not has_bounded_tardiness(no_cpus, tasks):
65 return None
66 # First uniformly reduce scheduler priority points to derive analysis
67 # priority points. Due to uniform reduction, does not change scheduling
68 # decisions. Shown in EA'12 to improve bounds.
69 min_priority_point = min(sched_pps)
70 analysis_pps = [sched_pps[i] - min_priority_point
71 for i in range(len(sched_pps))]
72
73 #Initialize S_i and Y_ints
74 S_i = [0]*len(tasks)
75 Y_ints = [0]*len(tasks)
76 # Calculate S_i and y-intercept (for G(\vec{x}) contributors)
77 for i, task in enumerate(tasks):
78 S_i[i] = max(0, task.cost * (1 - analysis_pps[i] / task.period))
79 Y_ints[i] = (0 - task.cost / no_cpus) * task.utilization() + task.cost \
80 - S_i[i]
81
82 if rounds == 0:
83 s = compute_exact_s(no_cpus, tasks, sum(S_i), Y_ints)
84 else:
85 s = compute_binsearch_s(no_cpus, tasks, sum(S_i), Y_ints, rounds)
86
87 details = AnalysisDetails(len(tasks))
88 details.bounds = [int(ceil(s - tasks[i].cost / no_cpus + tasks[i].cost
89 + analysis_pps[i]))
90 for i in range(len(tasks))]
91 details.S_i = S_i
92 details.G_i = [Y_ints[i] + s * tasks[i].utilization()
93 for i in range(len(tasks))]
94 return details
95
96def compute_exact_s(no_cpus, tasks, S, Y_ints):
97 replacements = []
98 for i, task1 in enumerate(tasks):
99 for j in range(i+1, len(tasks)):
100 task2 = tasks[j]
101 # Parallel lines do not intersect, and choice of identical lines
102 # doesn't matter. Ignore all such cases.
103 if task1.utilization() != task2.utilization():
104 intersect = (Y_ints[j] - Y_ints[i]) \
105 / (task1.utilization() - task2.utilization())
106 if intersect >= 0.0:
107 if task1.utilization() < task2.utilization():
108 replacements.append( (intersect, i, j) )
109 else:
110 replacements.append( (intersect, j, i) )
111 # Break ties by order of increasing utilization of replaced tasks. Avoids
112 # an edge case. Consider tasks A, B, C, in order of decreasing
113 # utilization. If the top m-1 items include tasks B and C, it is possible
114 # (without the tie break) to have the following replacements:
115 # C->B (no change)
116 # B->A (now A and C in set considered)
117 # C->A (no change)
118 #
119 # However, proper behavior should include A and B in considered set. The
120 # tie break avoids this case.
121 replacements.sort(key=lambda r: (r[0], tasks[r[1]].utilization()))
122
123 # The piecewise linear function we are tracing is G(s) + S(\tau) - ms, as
124 # discussed (with L(s) in place of G(s)) in EGB'10. We start at s = 0 and
125 # trace its value each time a slope changes, hoping for a value of zero.
126
127 # While tracing piecewise linear function, keep track of whether each task
128 # contributes to G(\vec{x}). Array implementation allows O(1) lookups and
129 # changes.
130 task_pres = [False]*len(tasks)
131
132 # Initial value and slope.
133 current_value = S
134 current_slope = -1 * no_cpus
135
136 init_pairs = heapq.nlargest(no_cpus - 1, enumerate(Y_ints),
137 key=lambda p: p[1])
138
139 # For s = 0, just use Y-intercepts to determine what is present.
140 for pair in init_pairs:
141 task_pres[pair[0]] = True
142 current_value += pair[1]
143 current_slope += tasks[pair[0]].utilization()
144
145 # Index of the next replacement
146 rindex = 0
147 next_s = 0.0
148 zero = float("inf")
149 while zero > next_s:
150 current_s = next_s
151 zero = current_s - current_value / current_slope
152 if rindex < len(replacements):
153 replacement = replacements[rindex]
154 next_s = replacement[0]
155 current_value += (next_s - current_s) * current_slope
156 # Apply replacement, if appropriate.
157 if task_pres[replacement[1]] and not task_pres[replacement[2]]:
158 task_pres[replacement[1]] = False
159 current_slope -= tasks[replacement[1]].utilization()
160 task_pres[replacement[2]] = True
161 current_slope += tasks[replacement[2]].utilization()
162 rindex += 1
163 else:
164 next_s = float("inf")
165 return zero
166
167def compute_binsearch_s(no_cpus, tasks, S, Y_ints, rounds):
168 def M(s):
169 Gvals = heapq.nlargest(no_cpus - 1,
170 [Y_ints[i] + tasks[i].utilization() * s
171 for i in range(len(tasks))])
172 return sum(Gvals) + S - no_cpus * s
173
174 min_s = 0.0
175 max_s = 1.0
176 while M(max_s) > 0:
177 min_s = max_s
178 max_s *= 2.0
179
180 for i in range(rounds):
181 middle = (max_s + min_s) / 2.0
182 if M(middle) < 0:
183 max_s = middle
184 else:
185 min_s = middle
186
187 #max_s is guaranteed to be a legal bound.
188 return max_s
189
190def has_bounded_tardiness(no_cpus, tasks):
191 return tasks.utilization() <= no_cpus and \
192 forall(tasks)(lambda t: t.period >= t.cost)
193
194def bound_gedf_response_times(no_cpus, tasks, rounds):
195 if schedcat.sched.using_native:
196 native_ts = schedcat.sched.get_native_taskset(tasks)
197 gel_obj = native.GELPl(native.GELPl.GEDF,
198 no_cpus, native_ts, rounds)
199 return bound_response_times_from_native(no_cpus, tasks, gel_obj)
200
201 else:
202 response_details = compute_gedf_response_details(no_cpus, tasks, rounds)
203
204 return bound_response_times(tasks, response_details)
205
206def bound_gfl_response_times(no_cpus, tasks, rounds):
207 if schedcat.sched.using_native:
208 native_ts = schedcat.sched.get_native_taskset(tasks)
209 gel_obj = native.GELPl(native.GELPl.GFL,
210 no_cpus, native_ts, rounds)
211 return bound_response_times_from_native(no_cpus, tasks, gel_obj)
212
213 else:
214 response_details = compute_gfl_response_details(no_cpus, tasks, rounds)
215
216 return bound_response_times(tasks, response_details)
217
218def bound_response_times(tasks, response_details):
219 if response_details is None:
220 return False
221
222 else:
223 for i in range(len(tasks)):
224 tasks[i].response_time = response_details.bounds[i]
225 return True
226
227def bound_response_times_from_native(no_cpus, tasks, gel_obj):
228 if has_bounded_tardiness(no_cpus, tasks):
229 for i in range(len(tasks)):
230 tasks[i].response_time = gel_obj.get_bound(i)
231 return True
232
233 else:
234 return False