Dyn Multi Alg
Dyn Multi Alg
December 5, 2005
6.046J/18.410J
Handout 29
December 5, 2005
Abstract
This tutorial teaches dynamic multithreaded algorithms using a Cilk-like [11, 8, 10] model.
The material was taught in the MIT undergraduate class 6.046 Introduction to Algorithms as
two 80-minute lectures. The style of the lecture notes follows that of the textbook by Cormen,
Leiserson, Rivest, and Stein [7], but the pseudocode from that textbook has been Cilkied
to allow it to describe multithreaded algorithms. The rst lecture teaches the basics behind
multithreading, including dening the measures of work and critical-path length. It culminates
in the greedy scheduling theorem due to Graham and Brent [9, 6]. The second lecture shows
how parallel applications, including matrix multiplication and sorting, can be analyzed using
divide-and-conquer recurrences.
1.1 Model
Our model of dynamic multithreaded computation is based on the procedure abstraction found in
virtually any programming language. As an example, the procedure F IB gives a multithreaded
algorithm for computing the Fibonacci numbers:1
Support was provided in part by the Defense Advanced Research Projects Agency (DARPA) under Grant F3060297-1-0270, by the National Science Foundation under Grants EIA-9975036 and ACI-0324974, and by the SingaporeMIT Alliance.
1
This algorithm is a terrible way to compute Fibonacci numbers, since it runs in exponential time when logarithmic
methods are known [7, pp. 902903], but it serves as a good didactic example.
F IB (n)
1 if n < 2
2
then return n
3 x spawn F IB (n 1)
4 y spawn F IB (n 2)
5 sync
6 return (x + y)
A spawn is the parallel analog of an ordinary subroutine call. The keyword spawn before the
subroutine call in line 3 indicates that the subprocedure F IB (n 1) can execute in parallel with
the procedure F IB (n) itself. Unlike an ordinary function call, however, where the parent is not
resumed until after its child returns, in the case of a spawn, the parent can continue to execute in
parallel with the child. In this case, the parent goes on to spawn F IB (n 2). In general, the parent
can continue to spawn off children, producing a high degree of parallelism.
A procedure cannot safely use the return values of the children it has spawned until it executes
a sync statement. If any of its children have not completed when it executes a sync, the procedure
suspends and does not resume until all of its children have completed. When all of its children
return, execution of the procedure resumes at the point immediately following the sync statement.
In the Fibonacci example, the sync statement in line 5 is required before the return statement
in line 6 to avoid the anomaly that would occur if x and y were summed before each had been
computed.
The spawn and sync keywords specify logical parallelism, not actual parallelism. That is,
these keywords indicate which code may possibly execute in parallel, but what actually runs in
parallel is determined by a scheduler, which maps the dynamically unfolding computation onto
the available processors.
We can view a multithreaded computation in graph-theoretic terms as a dynamically unfolding
dag G = (V, E), as is shown in Figure 1 for F IB . We dene a thread to be a maximal sequence
of instructions not containing the parallel control statements spawn , sync, and return . Threads
make up the set V of vertices of the multithreaded computation dag G. Each procedure execution is
a linear chain of threads, each of which is connected to its successor in the chain by a continuation
edge. When a thread u spawns a thread v, the dag contains a spawn edge (u, v) E, as well
as a continuation edge from u to us successor in the procedure. When a thread u returns, the
dag contains an edge (u, v), where v is the thread that immediately follows the next sync in the
parent procedure. Every computation starts with a single initial thread and (assuming that the
computation terminates), ends with a single nal thread. Since the procedures are organized in a
tree hierarchy, we can view the computation as a dag of threads embedded in the tree of procedures.
111
111
000
000
111
111
000
000
111
111
000
000
111
111
000
111
111
000fib(4)000
000
1111
111
0000
000
1111
111
0000
000
1111
111
0000
000
1111
111
0000
000
1111
111
0000 fib(3)
000
111
111
000
111
111
000 000
000
111
111
000
000
111
111
000
000
111
111
000
000
111
111
000 fib(2)
000
111
000
111
000
111
000
111
000
111
000
111
000
fib(1)
111
000
111
000
111
000
111
000
111
000
111
000
111
000
11
00
11
00
11
00
11
00
11
00
fib(1)
111
000
111
000
111
000
1111
0000
1111
0000
1111
0000
1111
111
0000
000
1111
111
0000
000
1111
111
0000
000
1111
111
0000
000
1111
111
0000fib(2)
000
11
00
11
00
11
00
11
00
11
00
fib(1)
111
000
111
000
111
000
111
000
111
000
fib(0)
11
00
11
00
11
00
11
00
11
00
11
00
fib(0)
Figure 1: A dag representing the multithreaded computation of F IB(4). Threads are shown as circles, and
each group of threads belonging to the same procedure are surrounded by a rounded rectangle. Downward
edges are spawns dependencies, horizontal edges represent continuation dependencies within a procedure,
and upward edges are return dependencies.
example, the computation in Figure 1. Suppose that every thread can be executed in unit time.
Then, the work of the computation is 17, and the critical-path length is 8.
When a multithreaded computation is executed on a given number P of processors, its running
time depends on how efficiently the underlying scheduler can execute it. Denote by TP the running
time of a given computation on P processors. Then, the work of the computation can be viewed
as T1 , and the critical-path length can be viewed as T .
The work and critical-path length can be used to provide lower bounds on the running time on
P processors. We have
TP T1 /P ,
(1)
since in one step, a P -processor computer can do at most P work. We also have
TP T ,
(2)
since a P -processor computer can do no more work in one step than an infinite-processor computer.
The speedup of a computation on P processors is the ratio T1 /TP , which indicates how many
times faster the P -processor execution is than a one-processor execution. If T1 /TP = (P ), then
we say that the P -processor execution exhibits linear speedup. The maximum possible speedup is
T1 /T , which is also called the parallelism of the computation, because it represents the average
amount of work that can be done in parallel for each step along the critical path. We denote the
parallelism of a computation by P .
given number of processors. It is up to the runtime scheduler to map the dynamically unfolding
computation onto the available processors so that the computation executes efciently. Good on
line schedulers are known [3, 4, 5] but their analysis is complicated. For simplicity, well illustrate
the principles behind these schedulers using an off-line greedy scheduler.
A greedy scheduler schedules as much as it can at every time step. On a P -processor computer,
time steps can be classied into two types. If there are P or more threads ready to execute, the step
is a complete step, and the scheduler executes any P threads of those ready to execute. If there are
fewer than P threads ready to execute, the step is an incomplete step, and the scheduler executes
all of them. This greedy strategy is provably good.
Theorem 1 (Graham [9], Brent [6]) A greedy scheduler executes any multithreaded computation
G with work T1 and critical-path length T in time
TP T1 /P + T
(3)
during the development of Socrates which was resolved by understanding the measures of work
and critical-path length.
The Socrates program was initially developed on a 32-processor computer at MIT, but it was
intended to run on a 512-processor computer at the National Center for Supercomputing Appli
cations (NCSA) at the University of Illinois. A clever optimization was proposed which, during
testing at MIT, caused the program to run much faster than the original program. Nevertheless, the
optimization was abandoned, because an analysis of work and critical-path length indicated that
the program would actually be slower on the NCSA machine.
Let us examine this anomaly in more detail. For simplicity, the actual timing numbers have
been simplied. The original program ran in T32 = 65 seconds at MIT on 32 processors. The
(4)
Exercise 1-4. Prove that the time for a greedy scheduler to execute any multithreaded computa
tion is within a factor of 2 of the time required by an optimal scheduler.
Exercise 1-5. For what number P of processors do the two chess programs described in this
section run equally fast?
Exercise 1-6. Professor Tweed takes some measurements of his (deterministic) multithreaded
program, which is scheduled using a greedy scheduler, and nds that T4 = 80 seconds and
T64 = 10 seconds. What is the fastest that the professors computation could possibly run on
10 processors? Use Inequality (4) and the two lower bounds from Inequalities (1) and (2) to derive
your answer.
C11 C12
C21 C22
A11 A12
A21 A22
B11 B12
B21 B22
M ULT (C, A, B, n)
1 if n = 1
2
then C[1, 1] A[1, 1] B[1, 1]
3 return
4 allocate a temporary matrix T [1 . . n, 1 . . n]
5 partition A, B, C, and T into (n/2) (n/2) submatrices
6 spawn M ULT (C11 , A11 , B11 , n/2)
7 spawn M ULT (C12 , A11 , B12 , n/2)
8 spawn M ULT (C21 , A21 , B11 , n/2)
9 spawn M ULT (C22 , A21 , B12 , n/2)
10 spawn M ULT (T11 , A12 , B21 , n/2)
11 spawn M ULT (T12 , A12 , B22 , n/2)
12 spawn M ULT (T21 , A22 , B21 , n/2)
13 spawn M ULT (T22 , A22 , B22 , n/2)
14 sync
15 A DD (C, T, n)
The matrix partitionings in line 5 of M ULT and line 4 of ADD take O(1) time, since only a constant
number of indexing operations are required.
To analyze this algorithm, let AP (n) be the P -processor running time of A DD on nn matrices,
and let MP (n) be the P -processor running time of M ULT on n n matrices. The work (running
time on one processor) for A DD can be expressed by the recurrence
A1 (n) = 4A1 (n/2) + (1)
= (n2 ) ,
which is the same as for the ordinary double-nested-loop serial algorithm. Since the spawned
procedures can be executed in parallel, the critical-path length for A DD is
A (n) = A (n/2) + (1)
= (lg n) .
The work for M ULT can be expressed by the recurrence
M1 (n) = 8M1 (n/2) + A1 (n)
= 8M1 (n/2) + (n2 )
= (n3 ) ,
which is the same as for the ordinary triple-nested-loop serial algorithm. The critical-path length
for M ULT is
M (n) = M (n/2) + (lg n)
= (lg2 n) .
Thus, the parallelism for M ULT is M1 (n)/M (n) = (n3 / lg2 n), which is quite high. To multiply
1000 1000 matrices, for example, the parallelism is (ignoring constants) about 10003/102 = 107 .
Most parallel computers have far fewer processors.
To achieve high performance, it is often advantageous for an algorithm to use less space,
because more space usually means more time. For the matrix-multiplication problem, we can
eliminate the temporary matrix T in exchange for reducing the parallelism. Our new algorithm
M ULT-A DD performs C C + A B using a similar divide-and-conquer strategy to M ULT .
M ULT-A DD (C, A, B, n)
1 if n = 1
2
then C[1, 1] C[1, 1] + A[1, 1] B[1, 1]
3 return
4 partition A, B, and C into (n/2) (n/2) submatrices
5 spawn M ULT-A DD (C11 , A11 , B11 , n/2)
6 spawn M ULT-A DD (C12 , A11 , B12 , n/2)
7 spawn M ULT-A DD (C21 , A21 , B11 , n/2)
8 spawn M ULT-A DD (C22 , A21 , B12 , n/2)
9 sync
10 spawn M ULT-A DD (C11 , A12 , B21 , n/2)
11 spawn M ULT-A DD (C12 , A12 , B22 , n/2)
12 spawn M ULT-A DD (C21 , A22 , B21 , n/2)
13 spawn M ULT-A DD (C22 , A22 , B22 , n/2)
14 sync
15 return
Let MAP (n) be the P -processor running time of M ULT-A DD on n n matrices. The work for
M ULT-A DD is MA1 (n) = (n3 ), following the same analysis as for M ULT , but the critical-path
length is now
MA (n) = 2MA (n/2) + (1)
= (n) ,
since only 4 recursive calls can be executed in parallel.
Thus, the parallelism is MA1 (n)/MA (n) = (n2 ). On 10001000 matrices, for example, the
parallelism is (ignoring constants) still quite high: about 10002 = 106 . In practice, this algorithm
often runs somewhat faster than the rst, since saving space often saves time due to hierarchical
memory.
10
1
l/2
A[l/2]
A
1
l
A[l/2]
j j + 1
A[l/2]
m
A[l/2]
Figure 2: Illustration of P-M ERGE . The median of array A is used to partition array B, and then the lower
portions of the two arrays are recursively merged, as, in parallel, are the upper portions.
11
(5)
We shall show that PM1 (n) = (n) using the substitution method. (Actually, the Akra-Bazzi
method [2], if you know it, is simpler.) We assume inductively that PM1 (n) an b lg n for some
12
constants a, b > 0. We have
PM1 (n)
=
=
=
since we can choose b large enough so that b(lg n + lg((1 ))) dominates (lg n). Moreover,
we can pick a large enough to satisfy the base conditions. Thus, PM1 (n) = (n), which is the
same work asymptotically as the ordinary, serial merging algorithm.
We can now reanalyze the M ERGE -S ORT using the P-M ERGE subroutine. The work T1 (n)
remains the same, but the worst-case critical-path length now satises
T (n) = T (n/2) + (lg2 n)
= (lg3 n) .
The parallelism is now (n lg n)/(lg3 n) = (n/ lg2 n).
Exercise 2-1. Give an efcient and highly parallel multithreaded algorithm for multiplying an
n n matrix A by a length-n vector x that achieves work (n2 ) and critical path (lg n). Analyze
the work and critical-path length of your implementation, and give the parallelism.
Exercise 2-2. Describe a multithreaded algorithm for matrix multiplication that achieves work
(n3 ) and critical path (lg n). Comment informally on the locality displayed by your algorithm
in the ideal cache model as compared with the two algorithms from this section.
Exercise 2-3. Write a Cilk program to multiply an n1 n2 matrix by an n2 n3 matrix in parallel.
Analyze the work, critical-path length, and parallelism of your implementation. Your algorithm
should be efcient even if any of n1 , n2 , and n3 are 1.
Exercise 2-4. Write a Cilk program to implement Strassens matrix multiplication algorithm in
parallel as efciently as you can. Analyze the work, critical-path length, and parallelism of your
implementation.
Exercise 2-5. Write a Cilk program to invert a symmetric and positive-denite matrix in parallel.
(Hint: Use a divide-and-conquer approach based on the ideas of Theorem 31.12 from [7].)
Exercise 2-6. Akl and Santoro [1] have proposed a merging algorithm in which the rst step is to
nd the median of all the elements in the two sorted input arrays (as opposed to the median of the
elements in the larger subarray, as is done in P-M ERGE ). Show that if the total number of elements
in the two arrays is n, this median can be found using (lg n) time on one processor in the worst
case. Describe a linear-work multithreaded merging algorithm based on this subroutine that has a
parallelism of (n/ lg2 n). Give and solve the recurrences for work and critical-path length, and
determine the parallelism. Implement your algorithm as a Cilk program.
13
Exercise 2-7. Generalize the algorithm from Exercise 2-6 to nd arbitrary order statistics. De
scribe a merge-sorting algorithm with (n lg n) work that achieves a parallelism of (n/ lg n).
(Hint: Merge many subarrays in parallel.)
Exercise 2-8. The length of a longest-common subsequence of two length-n sequences x and y
can be computed in parallel using a divide-and-conquer multithreaded algorithm. Denote by c[i, j]
the length of a longest common subsequence of x[1 . . i] and y[1 . . j]. First, the multithreaded
algorithm recursively computes c[i, j] for all i in the range 1 i n/2 and all j in the range
1 j n/2. Then, it recursively computes c[i, j] for 1 i n/2 and n/2 < j n, while in
parallel recursively computing c[i, j] for n/2 < i n and 1 j n/2. Finally, it recursively
computes c[i, j] for n/2 < i n and n/2 < j n. For the base case, the algorithm computes
c[i, j] in terms of c[i 1, j 1], c[i 1, j], and c[i, j 1] in the ordinary way, since the logic of
the algorithm guarantees that these three values have already been computed.
That is, if the dynamic programming tableau is broken into four pieces
I
II
III IV
then the recursive multithreaded code would look something like this:
I
spawn II
spawn III
sync
IV
return
Analyze the work, critical-path length, and parallelism of this algorithm. Describe and analyze
an algorithm that is asymptotically as efcient (same work) but more parallel. Make whatever
interesting observations you can. Write an efcient Cilk program for the problem.
References
[1] Selim G. Akl and Nicola Santoro. Optimal parallel merging and sorting without memory
conicts. IEEE Transactions on Computers, C-36(11), November 1987.
[2] M. Akra and L. Bazzi. On the solution of linear recurrence equations. Computational Opti
mization and Application, 10:195210, 1998.
[3] Robert D. Blumofe. Executing Multithreaded Programs Efciently. PhD thesis, Depart
ment of Electrical Engineering and Computer Science, Massachusetts Institute of Techno
logy, September 1995.
14