Futures, Scheduling, and Work Distribution
Companion slides for
The Art of Multiprocessor Programming
by Maurice Herlihy & Nir Shavit
Art of Multiprocessor Programming© Herlihy –Shavit 2007
2
How to write Parallel Apps?
Art of Multiprocessor Programming© Herlihy –Shavit 2007
3
Matrix Multiplication
Art of Multiprocessor Programming© Herlihy –Shavit 2007
4
Matrix Multiplication
Art of Multiprocessor Programming© Herlihy –Shavit 2007
5
Matrix Multiplication
class Worker extends Thread {
int row, col;
Worker(int row, int col) {
this.row = row; this.col = col;
}
public void run() {
double dotProduct = 0.0;
for (int i = 0; i < n; i++)
dotProduct += a[row][i] * b[i][col];
c[row][col] = dotProduct;
}}}
Art of Multiprocessor Programming© Herlihy –Shavit 2007
6
Matrix Multiplication
class Worker extends Thread {
int row, col;
Worker(int row, int col) {
this.row = row; this.col = col;
}
public void run() {
double dotProduct = 0.0;
for (int i = 0; i < n; i++)
dotProduct += a[row][i] * b[i][col];
c[row][col] = dotProduct;
}}}
a thread
Art of Multiprocessor Programming© Herlihy –Shavit 2007
7
Matrix Multiplication
class Worker extends Thread {
int row, col;
Worker(int row, int col) {
this.row = row; this.col = col;
}
public void run() {
double dotProduct = 0.0;
for (int i = 0; i < n; i++)
dotProduct += a[row][i] * b[i][col];
c[row][col] = dotProduct;
}}}
Which matrix entry to compute
Art of Multiprocessor Programming© Herlihy –Shavit 2007
8
Matrix Multiplication
class Worker extends Thread {
int row, col;
Worker(int row, int col) {
this.row = row; this.col = col;
}
public void run() {
double dotProduct = 0.0;
for (int i = 0; i < n; i++)
dotProduct += a[row][i] * b[i][col];
c[row][col] = dotProduct;
}}}
Actual computation
Art of Multiprocessor Programming© Herlihy –Shavit 2007
9
Matrix Multiplication
void multiply() {
Worker[][] worker = new Worker[n][n];
for (int row …)
for (int col …)
worker[row][col] = new Worker(row,col);
for (int row …)
for (int col …)
worker[row][col].start();
for (int row …)
for (int col …)
worker[row][col].join();
}
Art of Multiprocessor Programming© Herlihy –Shavit 2007
10
Matrix Multiplication
void multiply() {
Worker[][] worker = new Worker[n][n];
for (int row …)
for (int col …)
worker[row][col] = new Worker(row,col);
for (int row …)
for (int col …)
worker[row][col].start();
for (int row …)
for (int col …)
worker[row][col].join();
}
Create nxn threads
Art of Multiprocessor Programming© Herlihy –Shavit 2007
11
Matrix Multiplication
void multiply() {
Worker[][] worker = new Worker[n][n];
for (int row …)
for (int col …)
worker[row][col] = new Worker(row,col);
for (int row …)
for (int col …)
worker[row][col].start();
for (int row …)
for (int col …)
worker[row][col].join();
}
Start them
Art of Multiprocessor Programming© Herlihy –Shavit 2007
12
Matrix Multiplication
void multiply() {
Worker[][] worker = new Worker[n][n];
for (int row …)
for (int col …)
worker[row][col] = new Worker(row,col);
for (int row …)
for (int col …)
worker[row][col].start();
for (int row …)
for (int col …)
worker[row][col].join();
}
Wait for them to finish
Art of Multiprocessor Programming© Herlihy –Shavit 2007
13
Matrix Multiplication
void multiply() {
Worker[][] worker = new Worker[n][n];
for (int row …)
for (int col …)
worker[row][col] = new Worker(row,col);
for (int row …)
for (int col …)
worker[row][col].start();
for (int row …)
for (int col …)
worker[row][col].join();
}
Wait for them to finish
What’s wrong with this picture?
Start them
Create nxn threads
Art of Multiprocessor Programming© Herlihy –Shavit 2007
14
Thread Overhead
Art of Multiprocessor Programming© Herlihy –Shavit 2007
15
Thread Pools
Art of Multiprocessor Programming© Herlihy –Shavit 2007
16
Thread Pool = Abstraction
Art of Multiprocessor Programming© Herlihy –Shavit 2007
17
ExecutorService Interface
Art of Multiprocessor Programming© Herlihy –Shavit 2007
18
Future<T>
Callable<T> task = …;
…
Future<T> future = executor.submit(task);
…
T value = future.get();
Art of Multiprocessor Programming© Herlihy –Shavit 2007
19
Future<T>
Callable<T> task = …;
…
Future<T> future = executor.submit(task);
…
T value = future.get();
Submitting a Callable<T> task returns a Future<T> object
Art of Multiprocessor Programming© Herlihy –Shavit 2007
20
Future<T>
Callable<T> task = …;
…
Future<T> future = executor.submit(task);
…
T value = future.get();
The Future’s get() method blocks until the value is available
Art of Multiprocessor Programming© Herlihy –Shavit 2007
21
Future<?>
Runnable task = …;
…
Future<?> future = executor.submit(task);
…
future.get();
Art of Multiprocessor Programming© Herlihy –Shavit 2007
22
Future<?>
Runnable task = …;
…
Future<?> future = executor.submit(task);
…
future.get();
Submitting a Runnable task returns a Future<?> object
Art of Multiprocessor Programming© Herlihy –Shavit 2007
23
Future<?>
Runnable task = …;
…
Future<?> future = executor.submit(task);
…
future.get();
The Future’s get() method blocks until the computation is complete
Art of Multiprocessor Programming© Herlihy –Shavit 2007
24
Note
Art of Multiprocessor Programming© Herlihy –Shavit 2007
25
Matrix Addition
4 parallel additions
Art of Multiprocessor Programming© Herlihy –Shavit 2007
26
Matrix Addition Task
class AddTask implements Runnable {
Matrix a, b; // multiply this!
public void run() {
if (a.dim == 1) {
c[0][0] = a[0][0] + b[0][0]; // base case
} else {
(partition a, b into half-size matrices aij and bij)
Future<?> f00 = exec.submit(add(a00,b00));
…
Future<?> f11 = exec.submit(add(a11,b11));
f00.get(); …; f11.get();
…
}}
This is not real Java
code (see notes)
Art of Multiprocessor Programming© Herlihy –Shavit 2007
27
Matrix Addition Task
class AddTask implements Runnable {
Matrix a, b; // multiply this!
public void run() {
if (a.dim == 1) {
c[0][0] = a[0][0] + b[0][0]; // base case
} else {
(partition a, b into half-size matrices aij and bij)
Future<?> f00 = exec.submit(add(a00,b00));
…
Future<?> f11 = exec.submit(add(a11,b11));
f00.get(); …; f11.get();
…
}}
Base case: add directly
Art of Multiprocessor Programming© Herlihy –Shavit 2007
28
Matrix Addition Task
class AddTask implements Runnable {
Matrix a, b; // multiply this!
public void run() {
if (a.dim == 1) {
c[0][0] = a[0][0] + b[0][0]; // base case
} else {
(partition a, b into half-size matrices aij and bij)
Future<?> f00 = exec.submit(add(a00,b00));
…
Future<?> f11 = exec.submit(add(a11,b11));
f00.get(); …; f11.get();
…
}}
Constant-time operation
Art of Multiprocessor Programming© Herlihy –Shavit 2007
29
Matrix Addition Task
class AddTask implements Runnable {
Matrix a, b; // multiply this!
public void run() {
if (a.dim == 1) {
c[0][0] = a[0][0] + b[0][0]; // base case
} else {
(partition a, b into half-size matrices aij and bij)
Future<?> f00 = exec.submit(add(a00,b00));
…
Future<?> f11 = exec.submit(add(a11,b11));
f00.get(); …; f11.get();
…
}}
Submit 4 tasks
Art of Multiprocessor Programming© Herlihy –Shavit 2007
30
Matrix Addition Task
class AddTask implements Runnable {
Matrix a, b; // multiply this!
public void run() {
if (a.dim == 1) {
c[0][0] = a[0][0] + b[0][0]; // base case
} else {
(partition a, b into half-size matrices aij and bij)
Future<?> f00 = exec.submit(add(a00,b00));
…
Future<?> f11 = exec.submit(add(a11,b11));
f00.get(); …; f11.get();
…
}}
Let them finish
Art of Multiprocessor Programming© Herlihy –Shavit 2007
31
Dependencies
Art of Multiprocessor Programming© Herlihy –Shavit 2007
32
Fibonacci
{
n if n < 2
F(n)
F(n-1) + F(n-2) otherwise
Art of Multiprocessor Programming© Herlihy –Shavit 2007
33
Disclaimer
Digression…
r5 = 5**(.5)
gold = (r5+1)/2
def fib(n):
return round((gold**n-(gold)**(-n))/r5)
Art of Multiprocessor Programming© Herlihy –Shavit 2007
35
Multithreaded Fibonacci
class FibTask implements Callable<Integer> {
static ExecutorService exec = Executors.newCachedThreadPool();
int arg;
public FibTask(int n) {
arg = n;
}
public Integer call() {
if (arg > 2) {
Future<Integer> left = exec.submit(new FibTask(arg-1));
Future<Integer> right = exec.submit(new FibTask(arg-2));
return left.get() + right.get();
} else {
return 1;
}}}
Art of Multiprocessor Programming© Herlihy –Shavit 2007
36
Multithreaded Fibonacci
class FibTask implements Callable<Integer> {
static ExecutorService exec = Executors.newCachedThreadPool();
int arg;
public FibTask(int n) {
arg = n;
}
public Integer call() {
if (arg > 2) {
Future<Integer> left = exec.submit(new FibTask(arg-1));
Future<Integer> right = exec.submit(new FibTask(arg-2));
return left.get() + right.get();
} else {
return 1;
}}}
Parallel calls
Art of Multiprocessor Programming© Herlihy –Shavit 2007
37
Multithreaded Fibonacci
class FibTask implements Callable<Integer> {
static ExecutorService exec = Executors.newCachedThreadPool();
int arg;
public FibTask(int n) {
arg = n;
}
public Integer call() {
if (arg > 2) {
Future<Integer> left = exec.submit(new FibTask(arg-1));
Future<Integer> right = exec.submit(new FibTask(arg-2));
return left.get() + right.get();
} else {
return 1;
}}}
Pick up & combine results
Art of Multiprocessor Programming© Herlihy –Shavit 2007
38
Dynamic Behavior
Art of Multiprocessor Programming© Herlihy –Shavit 2007
39
Fib DAG
fib(4)
fib(3)
fib(2)
submit
get
fib(2)
fib(1)
fib(1)
fib(1)
fib(1)
fib(1)
Art of Multiprocessor Programming© Herlihy –Shavit 2007
40
Arrows Reflect Dependencies
fib(4)
fib(3)
fib(2)
submit
get
fib(2)
fib(1)
fib(1)
fib(1)
fib(1)
fib(1)
Art of Multiprocessor Programming© Herlihy –Shavit 2007
41
How Parallel is That?
Art of Multiprocessor Programming© Herlihy –Shavit 2007
42
Fib Work
fib(4)
fib(3)
fib(2)
submit
get
fib(2)
fib(1)
fib(1)
fib(1)
fib(1)
fib(1)
Art of Multiprocessor Programming© Herlihy –Shavit 2007
43
Fib Work
work is 17
1
2
3
8
4
7
6
5
9
14
10
13
12
11
15
16
17
Art of Multiprocessor Programming© Herlihy –Shavit 2007
44
Fib Critical Path
fib(4)
fib(3)
fib(2)
submit
get
fib(2)
fib(1)
fib(1)
fib(1)
fib(1)
fib(1)
Art of Multiprocessor Programming© Herlihy –Shavit 2007
45
Fib Critical Path
fib(4)
Critical path length is 8
1
8
2
7
3
6
4
5
Art of Multiprocessor Programming© Herlihy –Shavit 2007
46
Notation Watch
Art of Multiprocessor Programming© Herlihy –Shavit 2007
47
Simple Bounds
Art of Multiprocessor Programming© Herlihy –Shavit 2007
48
More Notation Watch
Art of Multiprocessor Programming© Herlihy –Shavit 2007
49
Matrix Addition
Art of Multiprocessor Programming© Herlihy –Shavit 2007
50
Matrix Addition
4 parallel additions
Art of Multiprocessor Programming© Herlihy –Shavit 2007
51
Addition
Art of Multiprocessor Programming© Herlihy –Shavit 2007
52
Addition
A1(n) = 4 A1(n/2) + Θ(1)
4 spawned additions
Partition, synch, etc
Art of Multiprocessor Programming© Herlihy –Shavit 2007
53
Addition
A1(n) = 4 A1(n/2) + Θ(1)
= Θ(n2)
Same as double-loop summation
Art of Multiprocessor Programming© Herlihy –Shavit 2007
54
Addition
A1(n) = n2
A1(n) / (4 A1(n/2)+1) = n2/(4 (n/2)2+1)
= n2/(n2+1) → 1 as n →∞
Check! But also check out
Master_theorem_(analysis_of_algorithms)
On Wikipedia
Art of Multiprocessor Programming© Herlihy –Shavit 2007
55
Addition
A∞(n) = A∞(n/2) + Θ(1)
spawned additions in parallel
Partition, synch, etc
Art of Multiprocessor Programming© Herlihy –Shavit 2007
56
Addition
A∞(n) = A∞(n/2) + Θ(1)
= Θ(log n)
Art of Multiprocessor Programming© Herlihy –Shavit 2007
57
Addition
A∞(n) = log n
A∞(n) / (A∞(n/2) + 1) =�(log n)/(log (n/2) + 1) =�(log n/(log n - log 2 + 1)→ 1 � as n →∞
check!
Art of Multiprocessor Programming© Herlihy –Shavit 2007
58
Matrix Multiplication Redux
Art of Multiprocessor Programming© Herlihy –Shavit 2007
59
Matrix Multiplication Redux
Art of Multiprocessor Programming© Herlihy –Shavit 2007
60
First Phase …
8 multiplications
Art of Multiprocessor Programming© Herlihy –Shavit 2007
61
Second Phase …
4 additions
Art of Multiprocessor Programming© Herlihy –Shavit 2007
62
Multiplication
M1(n) = 8 M1(n/2) + A1(n)
8 parallel multiplications
Final addition
Art of Multiprocessor Programming© Herlihy –Shavit 2007
63
Multiplication
M1(n) = 8 M1(n/2) + Θ(n2)
= Θ(n3)
Same as serial triple-nested loop
Art of Multiprocessor Programming© Herlihy –Shavit 2007
64
Multiplication
M1(n) = n3
M1(n) /( 8 M1(n/2) + n2 ) =
n3 /(8 (n/2)3+n2) =
n3/(n3+n2) → 1 as n →∞
check!
Art of Multiprocessor Programming© Herlihy –Shavit 2007
65
Multiplication
M∞(n) = M∞(n/2) + A∞(n)
Half-size parallel multiplications
Final addition
Art of Multiprocessor Programming© Herlihy –Shavit 2007
66
Multiplication
M∞(n) = M∞(n/2) + A∞(n)
= M∞(n/2) + Θ(log n)
= Θ(log2 n)
Art of Multiprocessor Programming© Herlihy –Shavit 2007
67
Multiplication
M∞(n) = log2 n
M∞(n) /( M∞(n/2) + log n) =
log2 n/(log2 (n/2)+log n) =
log2 n/((log n-log2)2+log n) =
log2 n/(log2 n+(1-2 log 2)log n+log2 2) =
log2 n/(log2 n+...) → 1 as n →∞
check!
Art of Multiprocessor Programming© Herlihy –Shavit 2007
68
Parallelism
More than number of processors on any real machine (but Fugaku has 7,299,072 cores and is the fastest machine in the world in 2020)
Art of Multiprocessor Programming© Herlihy –Shavit 2007
69
Shared-Memory Multiprocessors
Art of Multiprocessor Programming© Herlihy –Shavit 2007
70
Ideal Scheduling Hierarchy
Tasks
Processors
User-level scheduler
Art of Multiprocessor Programming© Herlihy –Shavit 2007
71
Realistic Scheduling Hierarchy
Tasks
Threads
Processors
User-level scheduler
Kernel-level scheduler
Art of Multiprocessor Programming© Herlihy –Shavit 2007
72
For Example
Art of Multiprocessor Programming© Herlihy –Shavit 2007
73
Speedup
the kernel gives us
Art of Multiprocessor Programming© Herlihy –Shavit 2007
74
Scheduling Hierarchy
over T steps is:
Art of Multiprocessor Programming© Herlihy –Shavit 2007
75
Greed is Good
Art of Multiprocessor Programming© Herlihy –Shavit 2007
76
Theorem
T ≤ T1/PA + T∞(P-1)/PA
Art of Multiprocessor Programming© Herlihy –Shavit 2007
77
Deconstructing
T ≤ T1/PA + T∞(P-1)/PA
Art of Multiprocessor Programming© Herlihy –Shavit 2007
78
Deconstructing
Assume P = PA
T ≤ T1/P + T∞(P-1)/P
When P=1, T=T1,
as P->∞, T-> T∞
Art of Multiprocessor Programming© Herlihy –Shavit 2007
79
Deconstructing
T ≤ T1/PA + T∞(P-1)/PA
Actual time
Art of Multiprocessor Programming© Herlihy –Shavit 2007
80
Deconstructing
T ≤ T1/PA + T∞(P-1)/PA
Work divided by processor average
Art of Multiprocessor Programming© Herlihy –Shavit 2007
81
Deconstructing
T ≤ T1/PA + T∞(P-1)/PA
Cannot do better than critical path length
Art of Multiprocessor Programming© Herlihy –Shavit 2007
82
Deconstructing
T ≤ T1/PA + T∞(P-1)/PA
The higher the average the better it is …
Art of Multiprocessor Programming© Herlihy –Shavit 2007
83
Proof Strategy
Bound this!
Art of Multiprocessor Programming© Herlihy –Shavit 2007
84
Put Tokens in Buckets
idle
work
Processor found work
Processor available but couldn’t find work
Art of Multiprocessor Programming© Herlihy –Shavit 2007
85
At the end ….
idle
work
Total #tokens =
Art of Multiprocessor Programming© Herlihy –Shavit 2007
86
At the end ….
idle
work
T1 tokens
Art of Multiprocessor Programming© Herlihy –Shavit 2007
87
Must Show
idle
work
≤ T∞(P-1) tokens
Art of Multiprocessor Programming© Herlihy –Shavit 2007
88
Idle Steps
Art of Multiprocessor Programming© Herlihy –Shavit 2007
89
Every Move You Make …
Art of Multiprocessor Programming© Herlihy –Shavit 2007
90
Counting Tokens
Art of Multiprocessor Programming© Herlihy –Shavit 2007
91
Recapitulating
Art of Multiprocessor Programming© Herlihy –Shavit 2007
92
Turns Out
Art of Multiprocessor Programming© Herlihy –Shavit 2007
93
Work Distribution
zzz…
Art of Multiprocessor Programming© Herlihy –Shavit 2007
94
Work Dealing
Yes!
Art of Multiprocessor Programming© Herlihy –Shavit 2007
95
The Problem with �Work Dealing
D’oh!
D’oh!
D’oh!
Art of Multiprocessor Programming© Herlihy –Shavit 2007
96
Work Stealing
No work…
zzz
Yes!
Art of Multiprocessor Programming© Herlihy –Shavit 2007
97
Lock-Free Work Stealing
Art of Multiprocessor Programming© Herlihy –Shavit 2007
98
Local Work Pools
Each work pool is a Double-Ended Queue
Art of Multiprocessor Programming© Herlihy –Shavit 2007
99
Work DEQueue1
pushBottom
popBottom
work
1. Double-Ended Queue
Art of Multiprocessor Programming© Herlihy –Shavit 2007
100
Obtain Work
popBottom
Art of Multiprocessor Programming© Herlihy –Shavit 2007
101
New Work
pushBottom
Art of Multiprocessor Programming© Herlihy –Shavit 2007
102
Whatcha Gonna do When the Well Runs Dry?
@&%$!!
empty
Art of Multiprocessor Programming© Herlihy –Shavit 2007
103
Steal Work from Others
Pick random guy’s DEQeueue
Art of Multiprocessor Programming© Herlihy –Shavit 2007
104
Steal this Thread!
popTop
Art of Multiprocessor Programming© Herlihy –Shavit 2007
105
Thread DEQueue
Never happen concurrently
}
Art of Multiprocessor Programming© Herlihy –Shavit 2007
106
Thread DEQueue
These are most common – make them fast
(minimize use of CAS)
Art of Multiprocessor Programming© Herlihy –Shavit 2007
107
Ideal
Art of Multiprocessor Programming© Herlihy –Shavit 2007
108
Compromise
Art of Multiprocessor Programming© Herlihy –Shavit 2007
109
Dreaded ABA Problem
top
CAS
Art of Multiprocessor Programming© Herlihy –Shavit 2007
110
Dreaded ABA Problem
top
Art of Multiprocessor Programming© Herlihy –Shavit 2007
111
Dreaded ABA Problem
top
Art of Multiprocessor Programming© Herlihy –Shavit 2007
112
Dreaded ABA Problem
top
Art of Multiprocessor Programming© Herlihy –Shavit 2007
113
Dreaded ABA Problem
top
Art of Multiprocessor Programming© Herlihy –Shavit 2007
114
Dreaded ABA Problem
top
Art of Multiprocessor Programming© Herlihy –Shavit 2007
115
Dreaded ABA Problem
top
Art of Multiprocessor Programming© Herlihy –Shavit 2007
116
Dreaded ABA Problem
top
CAS
Uh-Oh …
Yes!
Art of Multiprocessor Programming© Herlihy –Shavit 2007
117
Fix for Dreaded ABA
stamp
top
bottom
Art of Multiprocessor Programming© Herlihy –Shavit 2007
118
Bounded DEQueue
public class BDEQueue {
AtomicStampedReference<Integer> top;
volatile int bottom;
Runnable[] tasks;
…
}
Art of Multiprocessor Programming© Herlihy –Shavit 2007
119
Bounded DQueue
public class BDEQueue {
AtomicStampedReference<Integer> top;
volatile int bottom;
Runnable[] tasks;
…
}
Index & Stamp (synchronized)
Art of Multiprocessor Programming© Herlihy –Shavit 2007
120
Bounded DEQueue
public class BDEQueue {
AtomicStampedReference<Integer> top;
volatile int bottom;
Runnable[] deq;
…
}
Index of bottom thread (no need to synchronize
The effect of a write must be seen – so we need a memory barrier)
Art of Multiprocessor Programming© Herlihy –Shavit 2007
121
Bounded DEQueue
public class BDEQueue {
AtomicStampedReference<Integer> top;
volatile int bottom;
Runnable[] tasks;
…
}
Array holding tasks
Art of Multiprocessor Programming© Herlihy –Shavit 2007
122
pushBottom()
public class BDEQueue {
…
void pushBottom(Runnable r){
tasks[bottom] = r;
bottom++;
}
…
}
Art of Multiprocessor Programming© Herlihy –Shavit 2007
123
pushBottom()
public class BDEQueue {
…
void pushBottom(Runnable r){
tasks[bottom] = r;
bottom++;
}
…
}
Bottom is the index to store the new task in the array
Art of Multiprocessor Programming© Herlihy –Shavit 2007
124
pushBottom()
public class BDEQueue {
…
void pushBottom(Runnable r){
tasks[bottom] = r;
bottom++;
}
…
}
Adjust the bottom index
stamp
top
bottom
Art of Multiprocessor Programming© Herlihy –Shavit 2007
125
Steal Work
public Runnable popTop() {
int[] stamp = new int[1];
int oldTop = top.get(stamp), newTop = oldTop + 1;
int oldStamp = stamp[0], newStamp = oldStamp + 1;
if (bottom <= oldTop)
return null;
Runnable r = tasks[oldTop];
if (top.CAS(oldTop, newTop, oldStamp, newStamp)) return r;
return null;
}
Art of Multiprocessor Programming© Herlihy –Shavit 2007
126
Steal Work
public Runnable popTop() {
int[] stamp = new int[1];
int oldTop = top.get(stamp), newTop = oldTop + 1;
int oldStamp = stamp[0], newStamp = oldStamp + 1;
if (bottom <= oldTop)
return null;
Runnable r = tasks[oldTop];
if (top.CAS(oldTop, newTop, oldStamp, newStamp)) return r;
return null;
}
Read top (value & stamp)
Art of Multiprocessor Programming© Herlihy –Shavit 2007
127
Steal Work
public Runnable popTop() {
int[] stamp = new int[1];
int oldTop = top.get(stamp), newTop = oldTop + 1;
int oldStamp = stamp[0], newStamp = oldStamp + 1;
if (bottom <= oldTop)
return null;
Runnable r = tasks[oldTop];
if (top.CAS(oldTop, newTop, oldStamp, newStamp)) return r;
return null;
}
Compute new value & stamp
Art of Multiprocessor Programming© Herlihy –Shavit 2007
128
Steal Work
public Runnable popTop() {
int[] stamp = new int[1];
int oldTop = top.get(stamp), newTop = oldTop + 1;
int oldStamp = stamp[0], newStamp = oldStamp + 1;
if (bottom <= oldTop)
return null;
Runnable r = tasks[oldTop];
if (top.CAS(oldTop, newTop, oldStamp, newStamp)) return r;
return null;
}
Quit if queue is empty
stamp
top
bottom
Art of Multiprocessor Programming© Herlihy –Shavit 2007
129
Steal Work
public Runnable popTop() {
int[] stamp = new int[1];
int oldTop = top.get(stamp), newTop = oldTop + 1;
int oldStamp = stamp[0], newStamp = oldStamp + 1;
if (bottom <= oldTop)
return null;
Runnable r = tasks[oldTop];
if (top.CAS(oldTop, newTop, oldStamp, newStamp)) return r;
return null;
}
Try to steal the task
stamp
top
CAS
bottom
Art of Multiprocessor Programming© Herlihy –Shavit 2007
130
Steal Work
public Runnable popTop() {
int[] stamp = new int[1];
int oldTop = top.get(stamp), newTop = oldTop + 1;
int oldStamp = stamp[0], newStamp = oldStamp + 1;
if (bottom <= oldTop)
return null;
Runnable r = tasks[oldTop];
if (top.CAS(oldTop, newTop, oldStamp, newStamp)) return r;
return null;
}
Give up if
conflict occurs
Art of Multiprocessor Programming© Herlihy –Shavit 2007
131
Take Work
Runnable popBottom() {
if (bottom == 0) return null;
bottom--;
Runnable r = tasks[bottom];
int[] stamp = new int[1];
int oldTop = top.get(stamp), newTop = 0;
int oldStamp = stamp[0], newStamp = oldStamp + 1;
if (bottom > oldTop) return r;
if (bottom == oldTop){
bottom = 0;
if (top.CAS(oldTop, newTop, oldStamp, newStamp))
return r;
}
top.set(newTop,newStamp); return null;
}
Art of Multiprocessor Programming© Herlihy –Shavit 2007
132
Runnable popBottom() {
if (bottom == 0) return null;
bottom--;
Runnable r = tasks[bottom];
int[] stamp = new int[1];
int oldTop = top.get(stamp), newTop = 0;
int oldStamp = stamp[0], newStamp = oldStamp + 1;
if (bottom > oldTop) return r;
if (bottom == oldTop){
bottom = 0;
if (top.CAS(oldTop, newTop, oldStamp, newStamp))
return r;
}
top.set(newTop,newStamp); return null;
}
Take Work
Make sure queue is non-empty
Art of Multiprocessor Programming© Herlihy –Shavit 2007
133
Runnable popBottom() {
if (bottom == 0) return null;
bottom--;
Runnable r = tasks[bottom];
int[] stamp = new int[1];
int oldTop = top.get(stamp), newTop = 0;
int oldStamp = stamp[0], newStamp = oldStamp + 1;
if (bottom > oldTop) return r;
if (bottom == oldTop){
bottom = 0;
if (top.CAS(oldTop, newTop, oldStamp, newStamp))
return r;
}
top.set(newTop,newStamp); return null;
}
Take Work
Prepare to grab bottom task
Art of Multiprocessor Programming© Herlihy –Shavit 2007
134
Runnable popBottom() {
if (bottom == 0) return null;
bottom--;
Runnable r = tasks[bottom];
int[] stamp = new int[1];
int oldTop = top.get(stamp), newTop = 0;
int oldStamp = stamp[0], newStamp = oldStamp + 1;
if (bottom > oldTop) return r;
if (bottom == oldTop){
bottom = 0;
if (top.CAS(oldTop, newTop, oldStamp, newStamp))
return r;
}
top.set(newTop,newStamp); return null;
}
Take Work
Read top, & prepare new values
Art of Multiprocessor Programming© Herlihy –Shavit 2007
135
Runnable popBottom() {
if (bottom == 0) return null;
bottom--;
Runnable r = tasks[bottom];
int[] stamp = new int[1];
int oldTop = top.get(stamp), newTop = 0;
int oldStamp = stamp[0], newStamp = oldStamp + 1;
if (bottom > oldTop) return r;
if (bottom == oldTop){
bottom = 0;
if (top.CAS(oldTop, newTop, oldStamp, newStamp))
return r;
}
return null;
}
Take Work
If top & bottom 1 or more
apart, no conflict
stamp
top
bottom
Art of Multiprocessor Programming© Herlihy –Shavit 2007
136
Runnable popBottom() {
if (bottom == 0) return null;
bottom--;
Runnable r = tasks[bottom];
int[] stamp = new int[1];
int oldTop = top.get(stamp), newTop = 0;
int oldStamp = stamp[0], newStamp = oldStamp + 1;
if (bottom > oldTop) return r;
if (bottom == oldTop){
bottom = 0;
if (top.CAS(oldTop, newTop, oldStamp, newStamp))
return r;
}
top.set(newTop,newStamp); return null;
}
Take Work
At most one item left
stamp
top
bottom
Art of Multiprocessor Programming© Herlihy –Shavit 2007
137
Runnable popBottom() {
if (bottom == 0) return null;
bottom--;
Runnable r = tasks[bottom];
int[] stamp = new int[1];
int oldTop = top.get(stamp), newTop = 0;
int oldStamp = stamp[0], newStamp = oldStamp + 1;
if (bottom > oldTop) return r;
if (bottom == oldTop){
bottom = 0;
if (top.CAS(oldTop, newTop, oldStamp, newStamp))
return r;
}
top.set(newTop,newStamp); return null;
}
Take Work
Try to steal last item.
In any case reset Bottom
because the DEQueue will be empty
even if unsucessful (why?)
Art of Multiprocessor Programming© Herlihy –Shavit 2007
138
Runnable popBottom() {
if (bottom == 0) return null;
bottom--;
Runnable r = tasks[bottom];
int[] stamp = new int[1];
int oldTop = top.get(stamp), newTop = 0;
int oldStamp = stamp[0], newStamp = oldStamp + 1;
if (bottom > oldTop) return r;
if (bottom == oldTop){
bottom = 0;
if (top.CAS(oldTop, newTop, oldStamp, newStamp))
return r;
}
top.set(newTop,newStamp); return null;
}
Take Work
failed to get last item
Must still reset top
Art of Multiprocessor Programming© Herlihy –Shavit 2007
139
Runnable popBottom() {
if (bottom == 0) return null;
bottom--;
Runnable r = tasks[bottom];
int[] stamp = new int[1];
int oldTop = top.get(stamp), newTop = 0;
int oldStamp = stamp[0], newStamp = oldStamp + 1;
if (bottom > oldTop) return r;
if (bottom == oldTop){
bottom = 0;
if (top.CAS(oldTop, newTop, oldStamp, newStamp))
return r;
}
top.set(newTop,newStamp); return null;
}
Take Work
stamp
top
CAS
bottom
I win CAS
Art of Multiprocessor Programming© Herlihy –Shavit 2007
140
Runnable popBottom() {
if (bottom == 0) return null;
bottom--;
Runnable r = tasks[bottom];
int[] stamp = new int[1];
int oldTop = top.get(stamp), newTop = 0;
int oldStamp = stamp[0], newStamp = oldStamp + 1;
if (bottom > oldTop) return r;
if (bottom == oldTop){
bottom = 0;
if (top.CAS(oldTop, newTop, oldStamp, newStamp))
return r;
}
top.set(newTop,newStamp); return null;
}
Take Work
stamp
top
CAS
bottom
I lose CAS
Thief must
have won…
Art of Multiprocessor Programming© Herlihy –Shavit 2007
141
Runnable popBottom() {
if (bottom == 0) return null;
bottom--;
Runnable r = tasks[bottom];
int[] stamp = new int[1];
int oldTop = top.get(stamp), newTop = 0;
int oldStamp = stamp[0], newStamp = oldStamp + 1;
if (bottom > oldTop) return r;
if (bottom == oldTop){
bottom = 0;
if (top.CAS(oldTop, newTop, oldStamp, newStamp))
return r;
}
top.set(newTop,newStamp); return null;
}
Take Work
failed to get last item
Must still reset top
Art of Multiprocessor Programming© Herlihy –Shavit 2007
142
Old English Proverb
Art of Multiprocessor Programming© Herlihy –Shavit 2007
143
Variations
Art of Multiprocessor Programming© Herlihy –Shavit 2007
144
Work Balancing
5
2
b2+5 c/2=3
d2+5e/2=4
Art of Multiprocessor Programming© Herlihy –Shavit 2007
145
Work-Balancing Thread
public void run() {
int me = ThreadID.get();
while (true) {
Runnable task = queue[me].deq();
if (task != null) task.run();
int size = queue[me].size();
if (random.nextInt(size+1) == size) {
int victim = random.nextInt(queue.length);
int min = …, max = …;
synchronized (queue[min]) {
synchronized (queue[max]) {
balance(queue[min], queue[max]);
}}}}}
Art of Multiprocessor Programming© Herlihy –Shavit 2007
146
Work-Balancing Thread
public void run() {
int me = ThreadID.get();
while (true) {
Runnable task = queue[me].deq();
if (task != null) task.run();
int size = queue[me].size();
if (random.nextInt(size+1) == size) {
int victim = random.nextInt(queue.length);
int min = …, max = …;
synchronized (queue[min]) {
synchronized (queue[max]) {
balance(queue[min], queue[max]);
}}}}}
Keep running tasks
Art of Multiprocessor Programming© Herlihy –Shavit 2007
147
Work-Balancing Thread
public void run() {
int me = ThreadID.get();
while (true) {
Runnable task = queue[me].deq();
if (task != null) task.run();
int size = queue[me].size();
if (random.nextInt(size+1) == size) {
int victim = random.nextInt(queue.length);
int min = …, max = …;
synchronized (queue[min]) {
synchronized (queue[max]) {
balance(queue[min], queue[max]);
}}}}}
With probability
1/|queue|
Art of Multiprocessor Programming© Herlihy –Shavit 2007
148
Work-Balancing Thread
public void run() {
int me = ThreadID.get();
while (true) {
Runnable task = queue[me].deq();
if (task != null) task.run();
int size = queue[me].size();
if (random.nextInt(size+1) == size) {
int victim = random.nextInt(queue.length);
int min = …, max = …;
synchronized (queue[min]) {
synchronized (queue[max]) {
balance(queue[min], queue[max]);
}}}}}
Choose random victim
Art of Multiprocessor Programming© Herlihy –Shavit 2007
149
Work-Balancing Thread
public void run() {
int me = ThreadID.get();
while (true) {
Runnable task = queue[me].deq();
if (task != null) task.run();
int size = queue[me].size();
if (random.nextInt(size+1) == size) {
int victim = random.nextInt(queue.length);
int min = …, max = …;
synchronized (queue[min]) {
synchronized (queue[max]) {
balance(queue[min], queue[max]);
}}}}}
Lock queues in canonical order
Art of Multiprocessor Programming© Herlihy –Shavit 2007
150
Work-Balancing Thread
public void run() {
int me = ThreadID.get();
while (true) {
Runnable task = queue[me].deq();
if (task != null) task.run();
int size = queue[me].size();
if (random.nextInt(size+1) == size) {
int victim = random.nextInt(queue.length);
int min = …, max = …;
synchronized (queue[min]) {
synchronized (queue[max]) {
balance(queue[min], queue[max]);
}}}}}
Rebalance queues
Art of Multiprocessor Programming© Herlihy –Shavit 2007
151
Work Stealing & Balancing
Art of Multiprocessor Programming© Herlihy –Shavit 2007
152
T
O
M
M
R
A
V
L
O
O
R
I
D
L
E
D
Art of Multiprocessor Programming© Herlihy –Shavit 2007
153
I AM
LORD
VOLDEMORT
Art of Multiprocessor Programming© Herlihy –Shavit 2007
154
Limitations of Java Futures
import java.util.concurrent.*;
public class FibExec {
final static ExecutorService exec =
Executors.newFixedThreadPool(400);
static int fcall(int n) throws Exception {
if(n < 2)
return n;
Future<Integer> f1 = exec.submit(()->fcall(n-1));
int n2 = fcall(n-2);
return f1.get()+n2;
}
public static void main(String[] args) throws Exception {
for(int n=0;n<=45;n++) {
final int fibn = n;
Future<Integer> fib = exec.submit(()->fcall(fibn));
System.out.printf("fib(%d)=%d%n",n,fib.get());
}
exec.shutdown();
}}
Art of Multiprocessor Programming© Herlihy –Shavit 2007
155
Limitations of Java Futures
Art of Multiprocessor Programming© Herlihy –Shavit 2007
156
Better Java "Futures"
import java.util.concurrent.*;
public class FibExec2 {
final static ExecutorService exec = new ForkJoinPool(8);
static int fcall(int n) throws Exception {
if(n < 2) return n;
Future<Integer> f1 = exec.submit(()->fcall(n-1));
int n2 = fcall(n-2);
return f1.get()+n2;
}
public static void main(String[] args) throws Exception {
for(int n=0;n<=45;n++) {
final int fibn = n;
System.out.printf("fib(%d)=%d%n",n,fcall(n));
}
exec.shutdown();
}}
Art of Multiprocessor Programming© Herlihy –Shavit 2007
157
Limitations of Java Futures
Art of Multiprocessor Programming© Herlihy –Shavit 2007
158
Better Java "Futures" v3
public class FibExec3 {
final static ExecutorService exec = new ForkJoinPool(8);
public static<T> CompletableFuture<T> supplyAsyncFut(
Supplier<CompletableFuture<T>> supplier,
Executor executor) {...}
static CompletableFuture<Integer> fcall(int n) {
if (n < 2) return CompletableFuture.completedFuture(n);
CompletableFuture<Integer> f1 = supplyAsyncFut(()->fcall(n-1), exec);
CompletableFuture<Integer> f2 = fcall(n - 2);
return f1.thenCombine(f2, (a, b) -> a + b);
}}
Art of Multiprocessor Programming© Herlihy –Shavit 2007
159
Where
public static<T> CompletableFuture<T> supplyAsyncFut(
Supplier<CompletableFuture<T>> supplier,
Executor executor) {
final CompletableFuture<T> result = new CompletableFuture<>();
CompletableFuture.runAsync(() -> {
supplier.get().thenAccept((v) -> {
result.complete(v);
});
}, executor);
return result;
}
Art of Multiprocessor Programming© Herlihy –Shavit 2007
160
Better Java "Futures" v4
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ForkJoinPool;
import java.util.function.Supplier;
public class FibExec4 {
final static ExecutorService exec = new ForkJoinPool(8);
public static long fcallSync(int n) {
if( n < 2 ) return 1L;
return fcallSync(n-1) + fcallSync(n-2);
}
static CompletableFuture<Long> fcall(int n) {
if (n < 2) return CompletableFuture.completedFuture((long)n);
if (n < 23) return CompletableFuture.completedFuture(fcallSync(n));
final CompletableFuture<Long> f1 = supplyAsyncFut(()->fcall(n-1), exec);
final CompletableFuture<Long> f2 = fcall(n - 2);
return f1.thenCombine(f2, (a, b) -> a + b);
}
Art of Multiprocessor Programming© Herlihy –Shavit 2007
161
Futures in C++
#include <future>
#include <iostream>
int fib(int n) {
if(n < 2) return n;
std::future<int> n1 = std::async(std::launch::async,fib,n-1);
int n2 = fib(n-2);
return n1.get()+n2;
}
int main() {
for(int n=0;n<=45;n++) {
std::cout << "fib(" << n << ")=" << fib(n) << std::endl;
}
return 0;
}
Art of Multiprocessor Programming© Herlihy –Shavit 2007
162
Futures in C++
$ g++ -std=c++11 fibfut.cc -lpthread
$ ./a.out
fib(0)=0
fib(1)=1
fib(2)=1
fib(3)=2
fib(4)=3
fib(5)=5
fib(6)=8
fib(7)=13
fib(8)=21
...
Can handle fib(19) or so
Art of Multiprocessor Programming© Herlihy –Shavit 2007
163
Futures in C++
#include <future>
#include <iostream>
int fib(int n) {
if(n < 2) return n;
if(n > 23) {
std::future<int> n1 = std::async(std::launch::async,fib,n-1);
int n2 = fib(n-2);
return n1.get()+n2;
} else return fib(n-1)+fib(n-2);
}
int main() {
for(int n=0;n<=45;n++)
std::cout << "fib(" << n << ")=" << fib(n) << std::endl;
return 0;
}
Art of Multiprocessor Programming© Herlihy –Shavit 2007
164
Futures in C++
$ g++ -std=c++11 fibfut.cc -lpthread
$ ./a.out
fib(0)=0
fib(1)=1
fib(2)=1
fib(3)=2
fib(4)=3
fib(5)=5
fib(6)=8
fib(7)=13
fib(8)=21
...
Can handle fib(41) or so
Art of Multiprocessor Programming© Herlihy –Shavit 2007
165
Futures in C++
Art of Multiprocessor Programming© Herlihy –Shavit 2007
166
Futures in C++
#include <hpx/hpx.hpp>
#include <hpx/hpx_main.hpp>
#include <iostream>
int fib(int n) {
if(n < 2) return n;
if(n > 23) {
hpx::future<int> n1 = hpx::async(fib,n-1);
int n2 = fib(n-2);
return n1.get()+n2;
} else return fib(n-1)+fib(n-2);
}
int main(int argc,char **argv) {
for(int n=0;n<=45;n++)
std::cout << "fib(" << n << ")=" << fib(n) << std::endl;
return 0;
}
Art of Multiprocessor Programming© Herlihy –Shavit 2007
167
Actor Model
Art of Multiprocessor Programming© Herlihy –Shavit 2007
168
Actor Model Implementation
Art of Multiprocessor Programming© Herlihy –Shavit 2007
169
High Performance Computing
Art of Multiprocessor Programming© Herlihy –Shavit 2007
170
�This work is licensed under a Creative Commons Attribution-ShareAlike 2.5 License.