15-150 Fall 2020
Stephen Brookes
Lecture 18 Sequences and parallelism
15-150 Fall 2020 Lecture 18 Sequences and parallelism Stephen - - PowerPoint PPT Presentation
15-150 Fall 2020 Lecture 18 Sequences and parallelism Stephen Brookes announcements Election Day! TAs will be posting lab solution videos (canvas, Thursday or Friday) TAs offer NEW weekly REVIEW SESSION (Thursday, 6:30pm Pittsburgh
Stephen Brookes
Lecture 18 Sequences and parallelism
(canvas, Thursday or Friday)
(Thursday, 6:30pm Pittsburgh time)
signature SEQ = sig type 'a seq exception Range val nth : int -> 'a seq -> 'a val length : 'a seq -> int val tabulate : (int -> 'a) -> int -> 'a seq val empty : unit -> 'a seq val map : ('a -> 'b) -> ('a seq -> 'b seq) val split : 'a seq -> 'a seq * 'a seq val reduce : ('a * 'a -> 'a) -> 'a -> 'a seq -> 'a val mapreduce : ('a -> 'b) -> ('b * 'b -> 'b) -> 'b -> 'a seq -> 'b end
note the type of mapreduce
in many different ways
work/span characteristics
reduce g z S = g(reduce g z L, reduce g z R)
and we assumed g is constant-time, so we said
so we should have said
Wreduce(n) = 2Wreduce(n div 2) + 1 Wreduce(n) = 2Wreduce(n div 2) + O(n) Thanks to Sheng-Hsiang Sun for spotting this!
with known work/span characteristics
to some parallelizable problems
independent of the implementation
in the Seq structure, the equation is valid
(Both sides represent the same sequence)
different work, span
(This is obvious, if you think about it!)
map f S = tabulate (fn i => f(nth i S)) (length S)
nth i ⟨v0,…,vn-1⟩ = vi length ⟨v0,…,vn-1⟩ = n tabulate f n = ⟨f(0), …, f(n-1)⟩ empty( ) = ⟨ ⟩ if 0 ≤ i < n
nth i ⟨v0,…,vn-1⟩ = vi length ⟨v0,…,vn-1⟩ = n tabulate f n = ⟨f(0), …, f(n-1)⟩ empty( ) = ⟨ ⟩ if 0 ≤ i < n split ⟨v0,…,vn-1⟩ = (⟨v0,…,vm-1⟩, ⟨vm,…,vn-1⟩) where m = n div 2
fun reduce g z s = case (length s) of 0 => z | 1 => nth 0 s | _ => let val (s1, s2) = split s in g(reduce g z s1, reduce g z s2) end
reduce g z ⟨v1,…,vn⟩ = v1 g v2 … g vn
when g is total & associative, z an identity for g
fun mapreduce f g z s = case (length s) of 0 => z | 1 => f(nth 0 s) | _ => let val (s1, s2) = split s in g(mapreduce f g z s1, mapreduce f g z s2) end
mapreduce f g z ⟨v1,…,vn⟩ = (f v1) g (f v2) … g (f vn)
when g is total & associative, z an identity for g
associativity and identity elements
should only be used with suitable g, z
correctness for reduce
associativity and identity elements
should only be used with suitable g, z
correctness for reduce
You should be reading the notes, too!
reduce (op +) 0 ⟨v1, v2⟩ = (op +) (reduce (op +) 0 ⟨v1⟩, reduce (op +) 0 ⟨v2⟩) = (reduce (op +) 0 ⟨v1⟩) + (reduce (op +) 0 ⟨v2⟩) = (v1 + 0) + (v2 + 0) = v1 + v2
reduce g z behaves “correctly” when g is associative and z is an identity element
reduce (op +) 0 ⟨v1, v2⟩ = v1 + v2 + 0
reduce (op +) 21 ⟨v1, v2⟩ = (op +) (reduce (op +) 21 ⟨v1⟩, reduce (op +) 21 ⟨v2⟩) = (reduce (op +) 21 ⟨v1⟩) + (reduce (op +) 21 ⟨v2⟩) = (v1 + 21) + (v2 + 21) = v1 + v2 + 42 reduce (op +) 21 ⟨v1, v2⟩ ≠ v1 + v2 + 21
work and span of code
design correct code
and confirm cost analysis
implementing SEQ
expression work span nth i s O(1) O(1) length s O(1) O(1) tabulate f n O(n) O(1) empty( ) O(1) O(1) map f s O(n) O(1) reduce g z s O(n) O(log n)
mapreduce f g z s
O(n) O(log n)
split s
O(1) O(1)
when length of s is n, and f, g are constant time Assume we have an implementation of SEQ with
+ same specs as before
is very natural (!)
step-by-step trajectory, independently
and sequential evaluation
n bodies n2 forces
proportional to the product of the masses and the inverse square of the distance
Newton, 1687 F = G m1 m2 / r2
Law 4: There is no Law 4. Law 1: If an object experiences no net force, its velocity is constant:
Law 2: The acceleration of a body is parallel and proportional to the net force acting on the body, and inversely proportional to the mass of the body, i.e., F = m a. Law 3: When one body exerts a force F on a second body, the second body exerts an equal but opposite force −F on the first.
Velocity, force and acceleration are vectors
velocity + velocity = velocity acceleration + acceleration = acceleration scalar * acceleration = acceleration scalar * velocity = velocity speed = magnitude of velocity
Easy to generalize...
type body = point * real * vect type vect = real * real type point = real * real
type vect = real * real val zero : vect val add : vect * vect -> vect val scale : real * vect -> vect val mag : vect -> real signature VECT = sig end …
structure Vect : VECT = struct type vect = real * real val zero = (0.0, 0.0) fun add ((x1, y1), (x2, y2)) = (x1+x2 , y1+y2) fun scale(c, (x, y)) = (c * x , c * y) fun mag (x, y) = Math.sqrt (x * x + y * y) end
type point = real * real fun diff ((x1,y1):point, (x2,y2):point) : vect = (x2 - x1, y2 - y1) fun displace ((x,y):point, (x',y'):vect) : point = (x + x', y + y')
type body = point * real * vect val sun = ((0.0,0.0), 332000.0, (0.0,0.0)) val earth = ((1.0, 0.0), 1.0, (0.0,18.0)) distance from sun to earth = one “astronomical unit” sun is 332000 times more massive the sun’s (relative) velocity is zero mass, velocity position, ( )
accel : body -> body -> vect accel b1 b2 = acceleration on b1 due to gravitational attraction of b2
use default of zero when bodies are too close
fun accel (p1, _, _) (p2, m2, _) = let val d = diff(p1, p2) val r = mag d in if r < 0.1 then zero else scale(G * m2/(r*r*r) , d) end accel : body -> body -> vect accel b1 b2 = acceleration on b1 due to gravitational attraction of b2
use default of zero when bodies are too close
. p1 m1 p2 m2 . Gm2/r2 r = distance from p1 to p2 = acceleration on b1 due to b2 Gm2/r2 b1 b2
. p1 m1 p2 m2 . Gm1/r2 r = distance from p1 to p2 = acceleration on b2 due to b1 Gm1/r2 b1 b2
accels : body -> body seq -> vect
accels b s = net acceleration on b due to gravitational attraction
accels : body -> body seq -> vect
accels b s = net acceleration on b due to gravitational attraction
accels : body -> body seq -> vect
accels b s = net acceleration on b due to gravitational attraction
(vector sum)
accels : body -> body seq -> vect
accels b s = net acceleration on b due to gravitational attraction
(vector sum)
fun move (p, m, v) (a, dt) = let val dp = add(scale(dt,v), scale(0.5*dt*dt, a)) val dv = scale(dt, a) in (add(p, dp), m, add(v, dv)) end
move (p, m, v) (a, dt) = (p', m, v') v' = v + a dt p' = p + v dt + 1/2 a dt2 Newtonian calculus, too!
move (p, m, v) (a, dt) = (p’, m, v’) body at p, mass m, velocity v acted on by force F = m * a moves to p’ for time dt and its velocity changes to v’ v v’ a when m m p p’
p v a p’ = p + v dt + 1/2 a dt2
step : real -> body seq -> body seq
parallel evaluation
step dt ⟨b1, b2, ..., bN⟩ = ⟨b1’, b2’, ..., bN’⟩ where, for each i, bi’ = move bi (ai, dt) and ai = accels bi ⟨b1, b2, ..., bN⟩
accel bi bj accels bi ⟨b1, ..., bN⟩ move b (a, dt) step dt ⟨b1, ..., bN⟩
expression work span nth i s O(1) O(1) length s O(1) O(1) tabulate f n O(n) O(1) empty( ) O(1) O(1) map f s O(n) O(1) reduce g z s O(n) O(log n)
mapreduce f g z s
O(n) O(log n)
split s
O(1) O(1)
when length of s is n, and f, g are constant time Assume we have an implementation of SEQ with
accel (p1, m1, v1) (p2, m2, v2) = let val d = diff(p1, p2) val r = mag d in if r < 0.1 then zero else scale(G * m2/(r*r*r) , d) end
work, span O(1) accel b1 b2 has work O(1) span O(1)
accels bi ⟨b1, ..., bN⟩ = mapreduce (accel b) add zero ⟨b1, ..., bN⟩
work, span O(1) accels bi ⟨b1, ..., bN⟩ = mapreduce (accel b) add zero ⟨b1, ..., bN⟩
work, span O(1) accels bi ⟨b1, ..., bN⟩ = mapreduce (accel b) add zero ⟨b1, ..., bN⟩ mapreduce f g z ⟨b1, ..., bN⟩ applies f N times in parallel and combines using g f b1 f b2 ... f bN g ... g g
log2 N
cost graph
work, span O(1) accels bi ⟨b1, ..., bN⟩ = mapreduce (accel b) add zero ⟨b1, ..., bN⟩ has work O(N), span O(log N) accels bi ⟨b1, ..., bN⟩ mapreduce f g z ⟨b1, ..., bN⟩ applies f N times in parallel and combines using g f b1 f b2 ... f bN g ... g g
log2 N
cost graph
move (p, m, v) (a, dt) = let val p' = displace(p, add(scale(dt,v), scale(0.5*dt*dt, a))) val v' = add(v, scale(dt, a)) in (p', m, v') end
move (p, m, v) (a, dt) = let val p' = displace(p, add(scale(dt,v), scale(0.5*dt*dt, a))) val v' = add(v, scale(dt, a)) in (p', m, v') end
work, span O(1)
move (p, m, v) (a, dt) = let val p' = displace(p, add(scale(dt,v), scale(0.5*dt*dt, a))) val v' = add(v, scale(dt, a)) in (p', m, v') end
work, span O(1) work, span O(1)
move (p, m, v) (a, dt) = let val p' = displace(p, add(scale(dt,v), scale(0.5*dt*dt, a))) val v' = add(v, scale(dt, a)) in (p', m, v') end
work, span O(1) work, span O(1) has work, span O(1)
move (p, m, v) (a, dt)
step dt s = map (fn b => move b (accels b s, dt)) s
Let s be ⟨b1, ..., bN⟩
work O(N), span O(log N) step dt s = map (fn b => move b (accels b s, dt)) s
Let s be ⟨b1, ..., bN⟩
work O(N), span O(log N) map f s step dt s = map (fn b => move b (accels b s, dt)) s
Let s be ⟨b1, ..., bN⟩
work O(N), span O(log N) map f s step dt s = map (fn b => move b (accels b s, dt)) s
Let s be ⟨b1, ..., bN⟩
N sequential calls N parallel calls step dt ⟨b1, ..., bN⟩ has work O(N2), span O(log N)
O(1) O(1) O(N) O(log N) O(1) O(1) O(N2) O(log N)
accel bi bj accels bi ⟨b1, ..., bN⟩ move b (a, dt) step dt ⟨b1, ..., bN⟩ work span (using sequences)
accels bi ⟨b1, ..., bN⟩ step dt ⟨b1, ..., bN⟩ work span fun accels b (L : body list) = foldr add zero (List.map (accel b) L) fun step dt (L : body list) = List.map (fn b => move b (accels b L, dt)) L O(N) O(N) O(N2) O(N2) (using lists)
step-by-step trajectory, independently
step dt ⟨b1, ..., bN⟩ has work O(N2) step dt ⟨b1, ..., bN⟩ has span O(log N) step dt ⟨b1, ..., bN⟩ has span O(N2)
looks like an ellipse, as predicted by Kepler
val sun = ((0.0,0.0), 332000.0, (0.0,0.0)) val earth = ((1.0, 0.0), 1.0, (0.0,18.0)) val us : body seq = tabulate (fn 0 => sun | 1 => earth | _ => raise Range) 2 step us 0.01 =>* ⟨((5E~05,0.0),332000.0,(0.01,0.0)), ((~15.6,0.18),1.0,(~3320.0,18.0))⟩ us = ⟨sun, earth⟩
fun orbit b (n, dt) = if n=0 then [ ] else let val (p', m, v') = move b (accel b sun, dt) in p' :: orbit (p', m, v') (n-1, dt) end;
[(~15.6,0.18),(~48.7318019171,0.359213099043), (~81.7884162248,0.537587775754), (~114.835559608,0.71589462109), (~147.878962872,0.894177309445), (~180.920348361,1.07244756107), (~213.960467679,1.25071021688), (~246.999717285,1.42896774708), (~280.03833222,1.60722158368), (~313.076463412,1.78547263142)]
shaped like an ellipse
cluster of bodies with a single point mass at its barycenter
barycenter : (real * point) seq -> real * point barycenter ⟨(m1,p1),…,(mk,pk)⟩ = (M, P) M = m1+…+mk P = scale(1/M)(scale(m1,p1) +…+ scale(mk, pk))
signature Box = sig type box val inside : box -> point -> bool val quadrants : box -> box seq … end
datatype bhtree = Empty | Single of (real * point) | Quad of box * (real * point) * bhtree seq
INVARIANT: For every Quad (Box, (M, P), S)
bh : body seq -> bhtree
aspect : body * box * (real * point) -> real
.. . . .
aspect(b, Box, (M, P)) is smaller when Box has small diameter, P is far away from b, M not too large actual acceleration approximation ≈
bh_accels : real -> bhtree -> body -> vect ENSURES bh_accels theta T b2 = the acceleration on b2 due to the bodies in T, as computed by Barnes-Hut with threshold theta fun bh_accels theta Empty b2 = zero | bh_accels theta (Single b1) b2 = accel b1 b2 | bh_accels theta (Quad (Box, (M, P), S)) b2 = if aspect (b2, Box, (M, P)) > theta then mapreduce add zero (fn T => bh_accels theta T b2) S else accel (M, P) b2 end
Barnes-Hut algorithm in ML
use of invariant to guide code design
produce graphics simulations
functional programming(!)
when writing code
to prove correctness of code
to determine work/span of code
into how data is represented
based on signature AND specs AND work/span Seq.map f List.map f val (x, y) = (e1, e2) val x = e1; val y = e2 parallel sequential
arrays or balanced trees
ListSeq : SEQ ArraySeq : SEQ BalancedTreeSeq : SEQ
expression work span nth i s O(1) O(1) length s O(1) O(1) tabulate f n O(n) O(1) empty( ) O(1) O(1) map f s O(n) O(1) reduce g z s O(n log n) O(log n)
mapreduce f g z s
O(n log n) O(log n)
split s
O(n) O(1)
when length of s is n, and f, g are constant time What would change if we had an implementation of SEQ with