1 of 170

Futures, Scheduling, and Work Distribution

Companion slides for

The Art of Multiprocessor Programming

by Maurice Herlihy & Nir Shavit

2 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

2

How to write Parallel Apps?

  • How to
    • split a program into parallel parts
    • In an effective way
    • Thread management

3 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

3

Matrix Multiplication

 

4 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

4

Matrix Multiplication

 

5 of 170

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;

}}}

6 of 170

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

7 of 170

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

8 of 170

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

9 of 170

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();

}

10 of 170

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

11 of 170

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

12 of 170

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

13 of 170

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

14 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

14

Thread Overhead

  • Threads Require resources
      • Memory for stacks
      • Setup, teardown
  • Scheduler overhead
  • Worse for short-lived threads

15 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

15

Thread Pools

  • More sensible to keep a pool of long-lived threads
  • Threads assigned short-lived tasks
      • Runs the task
      • Rejoins pool
      • Waits for next assignment

​

16 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

16

Thread Pool = Abstraction

  • Insulate programmer from platform
      • Big machine, big pool
      • And vice-versa
  • Portable code
      • Runs well on any platform
      • No need to mix algorithm/platform concerns

​

17 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

17

ExecutorService Interface

  • In java.util.concurrent
      • Task = Runnable object
      • If no result value expected
      • Calls run() method.
      • Task = Callable<T> object
      • If result value of type T expected
      • Calls T call() method.

18 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

18

Future<T>

Callable<T> task = …;

…

Future<T> future = executor.submit(task);

…

T value = future.get();

19 of 170

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

20 of 170

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

21 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

21

Future<?>

Runnable task = …;

…

Future<?> future = executor.submit(task);

…

future.get();

22 of 170

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

23 of 170

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

24 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

24

Note

  • Executor Service submissions
      • Like New England traffic signs
      • Are purely advisory in nature
  • The executor
      • Like the New England driver
      • Is free to ignore any such advice
      • And could execute tasks sequentially …

25 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

25

Matrix Addition

4 parallel additions

26 of 170

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)

27 of 170

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

28 of 170

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

29 of 170

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

30 of 170

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

31 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

31

Dependencies

  • Matrix example is not typical
  • Tasks are independent
    • Don’t need results of one task …
    • To complete another
  • Often tasks are not independent

32 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

32

Fibonacci

{

n if n < 2

F(n)

F(n-1) + F(n-2) otherwise

  • Note
      • potential parallelism
      • Dependencies

​

33 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

33

Disclaimer

  • This Fibonacci implementation is
      • Egregiously inefficient
      • So don’t deploy it!
      • But illustrates our point
      • How to deal with dependencies
  • Exercise:
      • Make this implementation efficient!

34 of 170

Digression…

  • Efficient Fibonacci in Python

​

r5 = 5**(.5)

gold = (r5+1)/2

​

def fib(n):

return round((gold**n-(gold)**(-n))/r5)

​

35 of 170

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;

}}}

36 of 170

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

37 of 170

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

38 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

38

Dynamic Behavior

  • Multithreaded program is
    • A directed acyclic graph (DAG)
    • That unfolds dynamically
  • Each node is
    • A single unit of work

39 of 170

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)

40 of 170

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)

41 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

41

How Parallel is That?

  • Define work:
      • Total time on one processor
  • Define critical-path length:
      • Longest dependency path
      • Can’t beat that!

42 of 170

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)

43 of 170

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

44 of 170

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)

45 of 170

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

46 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

46

Notation Watch

  • TP = time on P processors
  • T1 = work (time on 1 processor)
  • T∞ = critical path length (time on ∞ processors)

​

47 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

47

Simple Bounds

  • TP ≥ T1/P
      • In one step, can’t do more than P work
  • TP ≥ T∞
      • Can’t beat infinite resources

48 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

48

More Notation Watch

  • Speedup on P processors
      • Ratio T1/TP
      • How much faster with P processors
  • Linear speedup
      • T1/TP = Θ(P)
  • Max speedup (average parallelism)
      • T1/T∞

49 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

49

Matrix Addition

50 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

50

Matrix Addition

4 parallel additions

51 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

51

Addition

  • Let AP(n) be running time
      • For n x n matrix
      • on P processors
  • For example
      • A1(n) is work
      • A∞(n) is critical path length

52 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

52

Addition

  • Work is

​

​

A1(n) = 4 A1(n/2) + Θ(1)

4 spawned additions

Partition, synch, etc

53 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

53

Addition

  • Work is

​

A1(n) = 4 A1(n/2) + Θ(1)

= Θ(n2)

​

Same as double-loop summation

54 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

54

Addition

  • Verify...

​

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

55 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

55

Addition

  • Critical Path length is

​

​

A∞(n) = A∞(n/2) + Θ(1)

spawned additions in parallel

Partition, synch, etc

56 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

56

Addition

  • Critical Path length is

​

​

A∞(n) = A∞(n/2) + Θ(1)

= Θ(log n)

​

57 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

57

Addition

  • Verify....

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!

58 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

58

Matrix Multiplication Redux

 

59 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

59

Matrix Multiplication Redux

 

60 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

60

First Phase …

 

8 multiplications

61 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

61

Second Phase …

 

4 additions

62 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

62

Multiplication

  • Work is

​

​

M1(n) = 8 M1(n/2) + A1(n)

8 parallel multiplications

Final addition

63 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

63

Multiplication

  • Work is

​

​

M1(n) = 8 M1(n/2) + Θ(n2)

= Θ(n3)

​

Same as serial triple-nested loop

64 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

64

Multiplication

  • Verify...

M1(n) = n3

M1(n) /( 8 M1(n/2) + n2 ) =

n3 /(8 (n/2)3+n2) =

n3/(n3+n2) → 1 as n →∞

​

​

check!

65 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

65

Multiplication

  • Critical path length is

​

​

M∞(n) = M∞(n/2) + A∞(n)

Half-size parallel multiplications

Final addition

66 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

66

Multiplication

  • Critical path length is

​

​

M∞(n) = M∞(n/2) + A∞(n)

= M∞(n/2) + Θ(log n)

= Θ(log2 n)

​

67 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

67

Multiplication

  • Verify...

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!

68 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

68

Parallelism

  • M1(n)/ M∞(n) = Θ(n3/log2 n)
  • To multiply two 1000 x 1000 matrices
      • 10003/102=107

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)

​

​

69 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

69

Shared-Memory Multiprocessors

  • Parallel applications
      • Do not have direct access to HW processors
  • Mix of other jobs
      • All run together
      • Come & go dynamically

70 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

70

Ideal Scheduling Hierarchy

Tasks

Processors

User-level scheduler

71 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

71

Realistic Scheduling Hierarchy

Tasks

Threads

Processors

User-level scheduler

Kernel-level scheduler

72 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

72

For Example

  • Initially,
      • All P processors available for application
  • Serial computation
      • Takes over one processor
      • Leaving P-1 for us
      • Waits for I/O
      • We get that processor back ….

73 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

73

Speedup

  • Map threads onto P processes
  • Cannot get P-fold speedup
      • What if the kernel doesn’t cooperate?
  • Can try for speedup proportional to
      • time-averaged number of processors

the kernel gives us

74 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

74

Scheduling Hierarchy

  • User-level scheduler
      • Tells kernel which threads are ready
  • Kernel-level scheduler
      • Synchronous (for analysis, not correctness!)
      • Picks pi threads to schedule at step i
      • Processor average

over T steps is:

​

 

75 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

75

Greed is Good

  • Greedy scheduler
      • Schedules as much as it can
      • At each time step
  • Optimal schedule is greedy (why?)
  • But not every greedy schedule is optimal

76 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

76

Theorem

  • Greedy scheduler ensures that

​

T ≤ T1/PA + T∞(P-1)/PA

​

77 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

77

Deconstructing

​

​

T ≤ T1/PA + T∞(P-1)/PA

​

78 of 170

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∞

​

79 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

79

Deconstructing

​

​

T ≤ T1/PA + T∞(P-1)/PA

​

Actual time

80 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

80

Deconstructing

​

​

T ≤ T1/PA + T∞(P-1)/PA

​

Work divided by processor average

81 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

81

Deconstructing

​

​

T ≤ T1/PA + T∞(P-1)/PA

​

Cannot do better than critical path length

82 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

82

Deconstructing

​

​

T ≤ T1/PA + T∞(P-1)/PA

​

The higher the average the better it is …

83 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

83

Proof Strategy

 

 

Bound this!

84 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

84

Put Tokens in Buckets

idle

work

Processor found work

Processor available but couldn’t find work

85 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

85

At the end ….

idle

work

Total #tokens =

 

86 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

86

At the end ….

idle

work

T1 tokens

87 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

87

Must Show

idle

work

≤ T∞(P-1) tokens

88 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

88

Idle Steps

  • An idle step is one where there is at least one idle processor
  • Only time idle tokens are generated
  • Focus on idle steps

89 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

89

Every Move You Make …

  • Scheduler is greedy
  • At least one node ready
  • Number of idle threads in one idle step
      • At most pi-1 ≤ P-1
  • How many idle steps?

90 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

90

Counting Tokens

  • At most P-1 idle threads per step
  • At most T∞ steps (critical path len)
  • So idle bucket contains at most
      • T∞(P-1) tokens
  • Both buckets contain
      • T1 + T∞(P-1) tokens

91 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

91

Recapitulating

 

 

 

92 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

92

Turns Out

  • This bound is within a factor of 2 of optimal
  • Actual optimal is NP-complete

93 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

93

Work Distribution

zzz…

94 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

94

Work Dealing

Yes!

95 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

95

The Problem with �Work Dealing

D’oh!

D’oh!

D’oh!

96 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

96

Work Stealing

No work…

zzz

Yes!

97 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

97

Lock-Free Work Stealing

  • Each thread has a pool of ready work
  • Remove work without synchronizing
  • If you run out of work, steal someone else’s
  • Choose victim at random

98 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

98

Local Work Pools

Each work pool is a Double-Ended Queue

99 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

99

Work DEQueue1

pushBottom

popBottom

work

1. Double-Ended Queue

100 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

100

Obtain Work

  • Obtain work
  • Run thread until
  • Blocks or terminates

popBottom

101 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

101

New Work

  • Unblock node
  • Spawn node

pushBottom

102 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

102

Whatcha Gonna do When the Well Runs Dry?

@&%$!!

empty

103 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

103

Steal Work from Others

Pick random guy’s DEQeueue

104 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

104

Steal this Thread!

popTop

105 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

105

Thread DEQueue

  • Methods
      • pushBottom
      • popBottom
      • popTop

Never happen concurrently

}

106 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

106

Thread DEQueue

  • Methods
      • pushBottom
      • popBottom
      • popTop

These are most common – make them fast

(minimize use of CAS)

107 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

107

Ideal

  • Wait-Free
  • Linearizable
  • Constant time

108 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

108

Compromise

  • Method popTop may fail if
    • Concurrent popTop succeeds, or a
    • Concurrent popBottom takes last work

109 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

109

Dreaded ABA Problem

top

CAS

110 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

110

Dreaded ABA Problem

top

111 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

111

Dreaded ABA Problem

top

112 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

112

Dreaded ABA Problem

top

113 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

113

Dreaded ABA Problem

top

114 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

114

Dreaded ABA Problem

top

115 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

115

Dreaded ABA Problem

top

116 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

116

Dreaded ABA Problem

top

CAS

Uh-Oh …

Yes!

117 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

117

Fix for Dreaded ABA

stamp

top

bottom

118 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

118

Bounded DEQueue

public class BDEQueue {

AtomicStampedReference<Integer> top;

volatile int bottom;

Runnable[] tasks;

…

}

119 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

119

Bounded DQueue

public class BDEQueue {

AtomicStampedReference<Integer> top;

volatile int bottom;

Runnable[] tasks;

…

}

Index & Stamp (synchronized)

120 of 170

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)

121 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

121

Bounded DEQueue

public class BDEQueue {

AtomicStampedReference<Integer> top;

volatile int bottom;

Runnable[] tasks;

…

}

Array holding tasks

122 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

122

pushBottom()

public class BDEQueue {

…

void pushBottom(Runnable r){

tasks[bottom] = r;

bottom++;

}

…

}

123 of 170

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

124 of 170

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

125 of 170

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;

}

126 of 170

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)

127 of 170

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

128 of 170

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

129 of 170

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

130 of 170

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

131 of 170

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;

}

132 of 170

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

133 of 170

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

134 of 170

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

135 of 170

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

136 of 170

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

137 of 170

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?)

138 of 170

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

139 of 170

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

140 of 170

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…

141 of 170

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

142 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

142

Old English Proverb

  • “May as well be hanged for stealing a sheep as a goat”
  • From which we conclude
      • Stealing was punished severely
      • Sheep were worth more than goats

143 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

143

Variations

  • Stealing is expensive
      • Pay CAS
      • Only one thread taken
  • What if
      • Randomly balance loads?

​

144 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

144

Work Balancing

5

2

b2+5 c/2=3

d2+5e/2=4

145 of 170

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]);

}}}}}

146 of 170

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

147 of 170

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|

148 of 170

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

149 of 170

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

150 of 170

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

151 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

151

Work Stealing & Balancing

  • Clean separation between app & scheduling layer
  • Works well when number of processors fluctuates.
  • Works on “black-box” operating systems

152 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

152

T

O

M

M

R

A

V

L

O

O

R

I

D

L

E

D

153 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

153

I AM

LORD

​

VOLDEMORT

154 of 170

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();

}}

​

155 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

155

Limitations of Java Futures

  • Eventually Blocks
  • Java implementation can only handle Fib(15) or so

156 of 170

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();

}}

​

157 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

157

Limitations of Java Futures

  • It is still based on blocking operations.
  • Relies on sophisticated ExecutorService to work well

158 of 170

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);

}}

​

​

159 of 170

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;

}

​

​

160 of 170

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);

}

161 of 170

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;

}

​

162 of 170

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

163 of 170

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;

}

​

164 of 170

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

165 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

165

Futures in C++

​

  • HPX
  • Main page - http://stellar.cct.lsu.edu
  • Source code - https://github.com/STEllAR-GROUP/hpx
  • Provides distributed futures-based computation for C++
  • Futures have .then() member, which does the job of continuations.

166 of 170

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;

}

​

167 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

167

Actor Model

  • Actors are objects that communicate with asynchronous messages
  • In response to a message,
      • an actor may change state
      • create more actors
      • send more messages

168 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

168

Actor Model Implementation

  • Messages are method calls
  • Each actor has a queue
      • Process incoming messages one at a time, gives coarse-grained concurrency
  • Return values are futures
  • No deadlocks, many races avoided, objects can move

169 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

169

High Performance Computing

  • Actors can move for load balancing
  • Asynchronous workflow between objects hides latency
  • Primary concept for Charm++

170 of 170

Art of Multiprocessor Programming© Herlihy –Shavit 2007

170

         �This work is licensed under a Creative Commons Attribution-ShareAlike 2.5 License.

  • You are free:
    • to Share — to copy, distribute and transmit the work
    • to Remix — to adapt the work
  • Under the following conditions:
    • Attribution. You must attribute the work to “The Art of Multiprocessor Programming” (but not in any way that suggests that the authors endorse you or your use of the work).
    • Share Alike. If you alter, transform, or build upon this work, you may distribute the resulting work only under the same, similar or a compatible license.
  • For any reuse or distribution, you must make clear to others the license terms of this work. The best way to do this is with a link to
    • http://creativecommons.org/licenses/by-sa/3.0/.
  • Any of the above conditions can be waived if you get permission from the copyright holder.
  • Nothing in this license impairs or restricts the author's moral rights.

​