Last time: * Thread idioms - Boss/worker model - Thread pool - Pipeline * Deferring work until after responding to requester Today: * Still not entirely sure how it's gonna work, but should be a cool lecture. * Data parallel vs. task parallel decompositions * Threads don't fit the data parallel model easily * Intel Threaded Building Blocks (TBB) - Generic programming - Functors Readings: Edward Lee paper on why threads are broken (XXX: notetaker tuned out for a bit) Let's suppose we have a function: void apply(double a[], size_t s, double (*fn)(double)) { for(size_t ii = 0; ii < 5; ii++) a[i] = fn(a[i]); } It applies a function to each element of an array. Here's the REQUIRES clause: //REQUIRES: fn has no MODIFIES clause. (i.e., fn is pure) This loop is embarassingly parallel - if you wanted, you could do all of the function calls completely in parallel. Imagine you had an N-way SMP with the following: const size_t BIG = <...>; double array[BIG]; double expensive(double d); How would you write a program that took best advantage of threads to solve this problem? Ideas: * Interleave (HA) * Slice into N chunks Discussion of the answer: struct arg { size_t sz, double * arr }; void thread(void * arg) { //apply to each chunk } void boss() { //fire all workers //join all n returned values } My, this sucks, because we do this all the time. TBB has a nice abstraction for this: parallel_for. template void parallel_for(const Range & r, const Body & b); Range is the method of dividing up the data. Body is the work to do. (Pause for questions, comment about abstraction) Let's talk about functions, closures, and lambdas. What does first-class mean? It means you can do four things: * Create * Destroy * Pass to functions * Return from functions In C++, functions are not first-class; you can only pass and return them, not create and destroy them. Here's a lambda in lisp: (lambda (x y) (* x y)) Not very interesting until you see: (lambda (x) (lambda (y) (* x y))) Pretend we called this byN: (by N 3) -> lambda () ((byN 3) 4) -> 12 This is called currying. (XXX: note taker is lazy and knows about currying) In C++: class byN { int n; public: byN(int x): n(x) {} int operator()(int y); }; int fact; cin >> fact; byN mult(fact); cout << mult(3) << endl; //prints 3 times fact Here's the meat of the functor: int byN::operator()(int y) { return n * y; } Here's a better example: byN by3(3); byN by4(4); cout << by3(5) << endl; //15 cout << by4(5) << endl; //20 Hooray, byN is effectively a function factory. At compile time, byN looks, for all the world, like a function. Question: couldn't we write something like byN with a template? Lists - templated vs. polymorphic List methods insert(T * o) Lists "of Objects" Object was an abstract type with a pure virtual function. In 280, we used polymorphism when types had "restrictions". bool hasA(list_t l, Predicate & p); class Predicate { public: virtual bool operator()(int) = 0; } class hasN : public Predicate { int n; public: bool operator()(int x) { return x == n; } hasN(int y) :n(y) {} }; Other way to write hasA: template bool hasA(list_t l, Predicate & p) { if(list_isEmpty(l)) return false; if(p(list_first(l))) return true; return hasA(list_rest(l), p); } //body is the same with or without templates Blah blah blah parametric polymorphism vs. ad-hoc polymorphism In the "polymorphic" case, we have explicitly communicated what types the Predicate must take as arguments. With the template, you can try to instantiate with ANY type whatsoever. Sorry.