Homework 5 Frequently Asked Questions Q1. How do I turn in? A1. Use the regular turnin command from CSIL: turnin hw5@cs240a betweennessCentrality.c report.pdf If you do not have a CSIL account then please email your submission to Adam (alugowski@cs.ucsb.edu) Q2. How can I resolve data races in Cilk? A2. The betweenness centrality algorithm will almost certainly have data races if naively parallelized. Cilk lets you get around races using hyperobjects. One hyperobject is reducer_opadd, which lets you do addition without worrying about races. http://www.cilk.com/resources-for-multicoders/for-developers-only/reducers-and-hyperobjects-example/ Hyperobjects are regular C++ objects, so you can have arrays of them as well: hyperobject hobj_array = new hyperobject[n]; ... hobj_array[i]() += 34; // increment object i ... val = hobj_array[i]().get_value(); // get the final value of object i ... delete [] hobj_array; // free the memory Q3. Cilk is good at divide and conquer. How does that work? A3. Divide and conquer is a good way to solve problems that can naturally be split into smaller problems, solved recursively, then assembled back into a solution for the original problem. This can also work to compute loops: void worker(int start, int end) { if (end - start > 500) { // problem is still fairly large, so divide cilk_spawn worker(start, (start+end)/2); worker((start+end)/2, end); cilk_sync; } else { // problem is small, so just do it for (int i = start; i < end; i++) { ...; } } } So to solve the entire problem: worker(0, n); One advantage of this approach over cilk_for is that you have more control over how memory is used. I.e. each worker can allocate its own local memory. Q4. Is there a way to get local, temporary storage for each Cilk strand? A4. Yes, you can use a Holder hyperobject. See the example in the Cilk documentation: http://web.cilk.com/_packages/documentation/index.htm?toc.htm?639.htm You basically declare your own class to wrap around the memory you want to keep local. The example uses a single int, but you could just as easily change that to a double, or even an array of doubles. If you wrap an entire array then be careful about memory allocation/free. Note also that if you're using the "worker" pattern from Q3 then each worker can just malloc() some memory. Q5. How is the graph structure laid out? A5. It may seem a bit convoluted at first, but the graph layout is very similar to the layout of a sparse matrix. Obviously n=# of vertecies and m=# of edges. endV stores the "other" endpoint of an edge, and numEdges gives the index of the first edge for a vertex. So to get the other endpoint of the first edge of vertex 0: endV[numEdges[0]] (the edge is between vertex 0 and vertex endV[numEdges[0]]) other endpoint of 2nd edge of vertex 0: endV[numEdges[0]+1] other endpoint of 1st edge of vertex 1: endV[numEdges[1]] So to find out how many edges vertex 23 has: numEdges[24]-numEdges[23]