-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathmakespan.lisp
77 lines (66 loc) · 2.87 KB
/
makespan.lisp
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
(defpackage :overlord/makespan
(:use :cl :alexandria :serapeum)
(:export :minimize-makespan
:optimal-machine-count))
(in-package :overlord/makespan)
;;; Minimize the makespan of a set of tasks.
;;; Bear in mind that minimizing a makespan is an NP-hard problem.
;;; Fortunately, there is an algorithm, the LPT algorithm, that gives
;;; theoretically good results, and even better results in practice.
;;; Our terminology differs slightly from that usually used in
;;; discussions of scheduling algorithms; what are usually called
;;; jobs, we will call tasks, to avoid confusion with the use of "job"
;;; in the rest of Overlord.
(defstruct machine
(total-time 0 :type (integer 0 *))
(task-queue (queue) :type queue))
(defconstructor task
(task t)
(time (integer 0 *)))
(defun machine-add-task (machine task)
(incf (machine-total-time machine)
(task-time task))
(enq task (machine-task-queue machine))
machine)
(defun machine-tasks (machine)
(qlist (machine-task-queue machine)))
(defun minimize-makespan (machine-count targets build-times)
"Given MACHINE-COUNT, a sequence of TARGETS, and a sequence of
BUILD-TIMES for each target, distribute TARGETS into batches, at most
one batch per machine (but possibly less), in such a way as to
minimize their makespan -- the time until the last machine is done
with the last task."
(assert (length= targets build-times))
(let* ((build-times
;; Add 1 to every build time so we get reasonable results
;; for targets with a build time of 0, which can happen
;; either because the target is too cheap to meter, or
;; because it hasn't been run yet.
(map 'vector #'1+ build-times))
(tasks (map 'vector #'task targets build-times)))
(lpt-schedule machine-count tasks)))
(defun optimal-machine-count (build-times)
(if (emptyp build-times) 0
(let ((max (extremum build-times #'>)))
(if (zerop max) (length build-times)
(let ((sum (reduce #'+ build-times)))
(ceiling (/ sum max)))))))
(defun lpt-schedule (machine-count tasks)
"Implement the Longest Processing Time algorithm.
MACHINE-COUNT should be an integer.
Times should be given as integers."
(check-type machine-count (integer 1 *))
(let ((tasks (sort-new tasks #'> :key #'task-time))
(heap (make-heap :size machine-count
:test #'<
:key #'machine-total-time)))
(loop repeat machine-count do
(heap-insert heap (make-machine)))
(do-each (task tasks)
(let* ((first-finished (heap-extract-maximum heap))
(machine (machine-add-task first-finished task)))
(heap-insert heap machine)))
(let* ((machines (reverse (heap-extract-all heap)))
(batches (map 'list #'machine-tasks machines)))
(loop for batch in batches
collect (map 'list #'task-task batch)))))