Today: More on parallel while. Reminder: Speedup is work(1 elt) / (work + overhead to walk the list) parallel_while is a templated class; templated over two types: * Body -- type of 1 node. * Stream -- advance by 1 node until done. Aside: project proposal should have a reason that you'll get a speedup. It's OK if you think there will be a speedup someday; * Doesn't work on 4-way parallelism, but might work with a ton more cores. * Your examples are too small but you think there might be big ones someday. struct list { elt e; list * next; }; void apply(list * l, elt (*fn)(elt)) { while(e) { l->e = fn(l->e); l = l->next; } } template class parallel_while { public: template void run(Stream & s, Body & b); }; class PWBody { elt (*f)(elt); public: PWBody(elt (*func)(elt)) : f(func) {} typedef list * argument_type; void operator()(argument_type l) const { l->e = f(l->e); } }; class ListNext { list * cur; public: ListNext(list * root) : cur(root) {} bool pop_if_present(list *& node) //if there is another element, set node to point to it and return //true. Otherwise, return false. { if(cur) { node = cur; cur = cur->next; } return cur; } }; Now with parallel_while magic: void list_apply(list * l, elt(*func)(elt)) { parallel_while w; PWBody b(func); ListNext n(l); w.run(n,b); } TODO: rewrite parallel_while example with sanity (elements instead of nodes). Also write a Stream class that adapts STL iterators. //This is not from lecture; it's a simple wrapper. Everyone and their //mother will have to write this. Yes, it has a dumb name; I'm on 5 //hours of sleep. template class STLStream { STLIterator cur, fin; public: STLStream(STLIterator begin, STLIterator end) :iter(inp) {} bool pop_if_present(std::iterator_traits::reference val) { if(cur != fin) { val = *cur; ++cur; return true; } return false; } }; //Here's my (Scott's) rewrite of the parallel_while example in the text struct Item { BigThing * data; //some type that is expensive to process Item * next; }; //original list processing code: void SerialApplyFooToList(Item * root) { for(Item * ptr = root; ptr != 0; ptr = ptr->next) { foo(ptr->data); } } //Example 4-2: pop_if_present for a parallel_while class ItemStream { Item * my_ptr; public: bool pop_if_present(BigThing * & next_thing) { if(my_ptr) { next_thing = my_ptr->data; my_ptr = my_ptr->next; } //NOTE: I'm not too worked up about this vs. explicit return //true & return false. return my_ptr; } ItemStream(Item * root) : my_ptr(root) {} }; class ApplyFoo //end rewrite of parallel_while example midterm: Median = 79, which is better than typical Mean = 79.5 Std dev about 12. Everyone is passing. Three common errors: 1) People tried to get clever on Question 1 about not using a lock when they thought didn't have to. They were almost always wrong. In particular, assignment of pthread_t is not atomic. There is also no null pthread_t. 2) Not putting things on the heap in question 2, which is a big chunk. 3) About a quarter of the class think that the Range is a container (it's not, it just contains the beginning and end of the container). The Body is a container (heh). Anywho, nonlinear recursive structures: struct tree { elt e; tree * left; tree * right; }; void tree_apply(tree * t, elt (*func)(elt)) { if(t) { t->e = func(t->e); tree_apply(t->left); tree_apply(t->right); } Sadly, our current parallel_while explanation doesn't work for this case because it's inherently linear. Enter parallel_while::add. void parallel_while::add(const Body::argument_type & node); //REQUIRES: called from Body::operator() In this case, the Stream is not particularly useful to us. class TBody { elt (*f)(elt); parallel_while & wh; public: TBody(elt (*func)(elt), parallel_while & w) : f(func), wh(w) {} typedef tree * argument_type; void operator()(argument_type t) const { t->e = f(t->e); if(t->left) wh.add(t->left); if(t->right) wh.add(t->right); } }; typedef class MTStream { public: bool pop_if_present(T & t) { return false; } }; void parallel_tree_apply(tree * t, elt (*fn)(elt)) { parallel_while w; TBody body(fn, w); MTStream mt; w.run(mt, body); } //curses! This will never start because the stream needs to give us //the root.