diff options
| author | Jeremy Erickson <jerickso@cs.unc.edu> | 2012-09-20 20:41:44 -0400 |
|---|---|---|
| committer | Jeremy Erickson <jerickso@cs.unc.edu> | 2012-09-25 12:39:56 -0400 |
| commit | 37dbd04e4f9d8956cf4be1c196e282760aa37011 (patch) | |
| tree | d2d2994c5dd697ec493555b22bbce999e1bfe4ba /schedcat | |
| parent | 8ba27be920e6bd47442e33946783a38c0fa37356 (diff) | |
Initial GEL Piecewise Linear implementation
Diffstat (limited to 'schedcat')
| -rw-r--r-- | schedcat/sched/edf/gel_pl.py | 234 |
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 | |||
| 10 | from __future__ import division | ||
| 11 | |||
| 12 | from math import ceil | ||
| 13 | |||
| 14 | from schedcat.util.quantor import forall | ||
| 15 | |||
| 16 | import schedcat.sched | ||
| 17 | if schedcat.sched.using_native: | ||
| 18 | import schedcat.sched.native as native | ||
| 19 | |||
| 20 | import heapq | ||
| 21 | |||
| 22 | class 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 | |||
| 29 | def 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 | |||
| 44 | def 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 | |||
| 58 | def 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 | |||
| 96 | def 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 | |||
| 167 | def 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 | |||
| 190 | def has_bounded_tardiness(no_cpus, tasks): | ||
| 191 | return tasks.utilization() <= no_cpus and \ | ||
| 192 | forall(tasks)(lambda t: t.period >= t.cost) | ||
| 193 | |||
| 194 | def 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 | |||
| 206 | def 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 | |||
| 218 | def 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 | |||
| 227 | def 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 | ||
