-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathMultiCoreScheduler.py
More file actions
322 lines (266 loc) · 14 KB
/
Copy pathMultiCoreScheduler.py
File metadata and controls
322 lines (266 loc) · 14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
import math
from pulp import LpProblem, LpMinimize, LpVariable, lpSum, getSolver
from MinCoreUsage import MinCoreUsage
from MinE2E import MinE2E
class MultiCoreScheduler:
def __init__(self):
self.formatted_tasks = None
self.tasks_instances = None
self.cores = None
self.assigned_vars = None
self.exec_start_vars = None
self.exec_end_vars = None
def multicore_core_scheduler(self, system, path, Config):
prob = LpProblem(f"Multicore_Core_Scheduling{path}", LpMinimize)
taskPeriods = [task["period"] for task in system["EntityStore"]]
taskOffsets = [task["initialOffset"] for task in system["EntityStore"]]
wcdts = [next(iter(device["delays"].values()))["wcdt"] for device in system["DeviceStore"]]
networkDelays = [delay["wcdt"] for delay in system["NetworkDelayStore"]]
tasks = [task for task in system["EntityStore"]]
self.cores = [core for core in system["CoreStore"]]
print(wcdts)
hyperPeriod = math.lcm(*taskPeriods)
hyperoffset = max(taskOffsets)
hyperDelay = 2 * max(wcdts) + max(networkDelays) # Over-approximation
print(f"Hyper-period: {hyperPeriod}")
# The task schedule is analysed over a scheduling window (makespan) such that
# all dependencies are satisfied at least once
makespan = system["PluginParameters"]["Makespan"]
schedulingWindow = (2 + math.ceil(hyperDelay / hyperPeriod)) * hyperPeriod + hyperoffset
schedulingWindow = max(schedulingWindow, makespan)
N = 2 * schedulingWindow
print(f"Scheduling window: {schedulingWindow} ns")
print(f"Big N: {N}")
print("Formatted tasks")
self.formatted_tasks = self.format_tasks(tasks, system.get("DependencyStore", None))
for task in self.formatted_tasks:
print(task)
self.tasks_instances = self.create_task_instances(schedulingWindow, tasks, N)
print("task instances")
for instances in self.tasks_instances:
for instance in instances["value"]:
print(instance)
# # # # # # # # # # # # #
# Variables
# assigned_(task,core)
# Variable for task instances, and their core assignment.
self.assigned_vars = LpVariable.dicts(
"assigned",
[f"{instance['name']},{core['name']}" for instance in self.tasks_instances for core in self.cores],
lowBound=0,
upBound=1,
cat="Binary",
)
# start_(task,instance)
# Variable for execution start time for each instance.
self.exec_start_vars = LpVariable.dicts(
"start",
[f"{instance['name']},{value['instance']}" for instance in self.tasks_instances for value in instance["value"]],
lowBound=0,
cat="Integer",
)
# end_(task,instance)
# Variable for execution end time for each instance.
self.exec_end_vars = LpVariable.dicts(
"end",
[f"{instance['name']},{value['instance']}" for instance in self.tasks_instances for value in instance["value"]],
lowBound=0,
cat="Integer",
)
# psi_tasks_(task_x,task_y)
# Variable for whether two tasks are allocated to different cores.
psi_tasks_vars = LpVariable.dicts(
"psi_tasks",
[MultiCoreScheduler.get_psi_tasks_key(task1['name'], task2['name']) for task1 in tasks for task2 in tasks if task1 != task2],
lowBound=0,
upBound=1,
cat="Binary",
)
# psi_task_core_(task_x,core_k,task_y,core_l)
# Variable for the possible pairing of tasks to cores.
psi_task_core_vars = LpVariable.dicts(
"psi_task_core",
[
MultiCoreScheduler.get_psi_task_core_key(task1['name'], core1['name'], task2['name'], core2['name'])
for core1 in self.cores for core2 in self.cores
for task1 in self.tasks_instances for task2 in self.tasks_instances if task1 != task2
],
lowBound=0,
upBound=1,
cat="Binary",
)
# bool_task_(task_x,instance_i,task_y,instance_j)
# Variable for whether task x executes sequentially after task y.
bool_task_vars = LpVariable.dicts(
"bool_task",
[
f"{task1['name']},{value1['instance']},{task2['name']},{value2['instance']}"
for task1 in self.tasks_instances for task2 in self.tasks_instances if task1 != task2
for value1 in task1["value"] for value2 in task2["value"]
],
lowBound=0,
upBound=1,
cat="Binary",
)
# # # # # # # # # # # # #
# Constraints
# 2a. A task's total execution time must be equal to the specified execution time.
# 2b. A task's execution start time must be greater than or equal to its LET start time,
# 2c. A task's execution end time must be less than or equal to its LET end time.
for task in self.tasks_instances:
wcet = self.get_wcet(task["name"])
for instance in filter(lambda x: x["instance"] != -1, task["value"]):
instance_name = f"{task['name']},{instance['instance']}"
prob += self.exec_end_vars[instance_name] - self.exec_start_vars[instance_name] == wcet
prob += self.exec_start_vars[instance_name] >= instance["letStartTime"]
prob += self.exec_end_vars[instance_name] <= instance["letEndTime"]
# 3. A task instance can only be assigned to one core.
for task in self.tasks_instances:
prob += lpSum(self.assigned_vars[f"{task['name']},{core['name']}"] for core in self.cores) == 1
# 4a, 4b, 4c. Pairs of tasks are allocated to the same core when each are allocated to the same core.
psi_task_core_considered = set()
for core1 in self.cores:
for core2 in self.cores:
for task1 in self.formatted_tasks:
for task2 in self.formatted_tasks:
if task1["name"] != task2["name"]:
task_pair = MultiCoreScheduler.get_psi_task_core_key(task1['name'], core1['name'], task2['name'], core2['name'])
# Break the symmetry because task ordering does not matter.
if (task_pair in psi_task_core_considered): continue
psi_task_core_considered.add(task_pair)
task_x = f"{task1['name']},{core1['name']}"
task_y = f"{task2['name']},{core2['name']}"
prob += psi_task_core_vars[task_pair] <= self.assigned_vars[task_x]
prob += psi_task_core_vars[task_pair] <= self.assigned_vars[task_y]
prob += psi_task_core_vars[task_pair] >= self.assigned_vars[task_x] + self.assigned_vars[task_y] - 1
# 4d. Pairs of tasks are not allocated to the same core when each are allocated to different cores.
psi_tasks_considered = set()
for task1 in self.formatted_tasks:
for task2 in self.formatted_tasks:
if task1["name"] != task2["name"]:
task_pair = MultiCoreScheduler.get_psi_tasks_key(task1['name'], task2['name'])
# Break the symmetry because task ordering does not matter.
if (task_pair in psi_tasks_considered): continue
psi_tasks_considered.add(task_pair)
prob += psi_tasks_vars[task_pair] == lpSum(
psi_task_core_vars[MultiCoreScheduler.get_psi_task_core_key(task1['name'], core1['name'], task2['name'], core2['name'])]
for core1 in self.cores for core2 in self.cores if core1 != core2
)
# 5a, 5b. If task x executes after task y on the same core, x's end time must be later than y's start time
# and y's end time must be earlier than x's start time.
for task1 in self.tasks_instances:
for task2 in self.tasks_instances:
if task1["name"] != task2["name"]:
for instance1 in filter(lambda x: x["instance"] != -1, task1["value"]):
for instance2 in filter(lambda x: x["instance"] != -1, task2["value"]):
task_x = f"{task1['name']},{instance1['instance']}"
task_y = f"{task2['name']},{instance2['instance']}"
instances_pair = f"{task_x},{task_y}"
task_pair = MultiCoreScheduler.get_psi_tasks_key(task1['name'], task2['name'])
prob += self.exec_end_vars[task_x] - self.exec_start_vars[task_y] <= N * bool_task_vars[instances_pair] + N * psi_tasks_vars[task_pair]
prob += self.exec_end_vars[task_y] - self.exec_start_vars[task_x] <= N - N * bool_task_vars[instances_pair] + N * psi_tasks_vars[task_pair]
if path == "/min-core-usage":
objective = MinCoreUsage()
objective.min_core_usage(self.assigned_vars, self.cores, self.tasks_instances, prob)
elif path == "/min-e2e-mc":
objective = MinE2E()
objective.min_e2e(N, system, prob, psi_task_core_vars, self)
prob.writeLP(Config.lpFile)
prob.solve(getSolver(Config.solverProg))
self.update_schedule()
schedule = {"EntityInstancesStore": self.tasks_instances}
for v in prob.variables():
print(f"{v.name} = {v.varValue}")
print(prob.sol_status)
return prob.sol_status, schedule
# For breaking symmetry because the task ordering does not matter.
@staticmethod
def get_psi_tasks_key(task1Name, task2Name):
if task1Name < task2Name: return f"{task1Name},{task2Name}"
else: return f"{task2Name},{task1Name}"
# For breaking symmetry because the task ordering does not matter.
@staticmethod
def get_psi_task_core_key(task1Name, core1Name, task2Name, core2Name):
if task1Name < task2Name: return f"{task1Name},{core1Name},{task2Name},{core2Name}"
else: return f"{task2Name},{core2Name},{task1Name},{core1Name}"
def format_tasks(self, tasks, dependencies):
formatted_tasks = []
for task in tasks:
source_tasks = self.get_source_tasks(task, dependencies)
device = self.get_device(task.get("core", None))
data = {
"name": task["name"],
"offset": task["activationOffset"],
"duration": task["duration"],
"period": task["period"],
"wcet": task["wcet"],
"requiredDevice": device,
"dependsOn": source_tasks,
}
formatted_tasks.append(data)
return formatted_tasks
def update_schedule(self):
for task in self.tasks_instances:
task["value"] = [instance for instance in task["value"] if instance["instance"] != -1]
for instance in task["value"]:
for core in self.cores:
if self.assigned_vars[f"{task['name']},{core['name']}"].varValue == 1:
start_time = self.exec_start_vars[f"{task['name']},{instance['instance']}"].varValue
end_time = self.exec_end_vars[f"{task['name']},{instance['instance']}"].varValue
execution_time = [
{
"core": core["name"],
"endTime": end_time,
"startTime": start_time,
}
]
instance["executionTime"] = self.get_wcet(task["name"])
instance["currentCore"] = core
instance["executionIntervals"] = execution_time
def create_task_instances(self, makespan, tasks, N):
task_instances = []
for task in tasks:
instances = []
number_of_instances = math.ceil((makespan - task["initialOffset"]) / task["period"])
instances.append(self.create_negative_instance(task, N))
for i in range(0, number_of_instances):
instances.append(self.create_task_instance(task, i))
data = {
"name": task["name"],
"type": "task",
"initialOffset": task["initialOffset"],
"value": instances,
}
task_instances.append(data)
return task_instances
def create_negative_instance(self, task, N):
return {
"instance": -1,
"letStartTime": -N,
"letEndTime": -N + task["duration"],
"executionTime": task["wcet"],
}
def create_task_instance(self, task, index):
periodStartTime = (index * task["period"]) + task["initialOffset"]
periodEndTime = periodStartTime + task["period"]
letStartTime = periodStartTime + task["activationOffset"]
letEndTime = letStartTime + task["duration"]
return {
"instance": index,
"periodStartTime": periodStartTime,
"periodEndTime": periodEndTime,
"letStartTime": letStartTime,
"letEndTime": letEndTime,
"executionTime": task["wcet"],
}
def get_wcet(self, task_name):
return next((task for task in self.formatted_tasks if task["name"] == task_name))["wcet"]
def get_device(self, core):
return next((c["device"] for c in self.cores if c["name"] == core), None)
def get_source_tasks(self, task, dependencies):
source_tasks = []
if dependencies is not None:
for dependency in filter(lambda x: x["source"]["entity"] != "__system" and x["destination"]["entity"] != "__system", dependencies):
if dependency["destination"]["entity"] == task["name"]:
source_tasks.append(dependency["source"]["entity"])
return source_tasks