I hoped to have written some skeletal code by now to show you, and start to point out the classes. However I find when I take on a software design, that I start to think about it a lot. I ponder how things interact while I’m in the shower, or find myself visualizing the program interactions, daydreaming while I’m cooking dinner or doing dishes. The past weekend has been filled with these moments, and now I’ll share my thoughts with you.
First, the class name transition is again misleading. I need a better name to represent the connections between nodes in the execution graph. The name link, or execution_path best convey the meaning. This is a small matter, but I wanted to give you the heads up.
Now, I’ll describe my thoughts over the weekend. Prepare yourself for a rant, and of course post comments if you have some ideas. The question is, what does the execution_graph execute… when it is “executed” something should be passed as an argument so that the execution_graph can base its decisions upon something. This might be an open file descriptor, an entity in a simulation, or perhaps absolutely nothing. Perhaps the decisions to make are programmed within the links themselves so arguments are not required. So let’s just pass a single thing, and even better let’s template this argument so a list of things could be passed. The state machine (I mean execution_graph) might be associated with one particular type, say ExecutionArgumentType.
This idea works well if we have a collection of things to process individually, say file descriptors for sockets. Each socket might be passed to an execution_graph for operations to take place. We can do even better, and use tbb::parallel_for with the auto_partitioner to divide up a vector of sockets and run their execution_graph in parallel. This is great for such easy examples, but what happens when we can’t do such a trivial division? What if we are processing something that requires fuzzy boundaries when we partition a space. For example, say we divide up a 2D plane into rectangles, and we have an algorithm which requires input from neighbors in a 2D cell for proper execution, like Conway’s game of life.
This is something that really ought to be handled by an execution_graph, but it’s not so easy unless the entire 2D board is passed as an argument. So the execution_graph can’t really do this kind of processing yet. But I’m not here to write Conway’s game of life in the execution_graph, I really don’t know how somebody would do it yet… but this doesn’t change the fact I need to give them the tools to do so. In my mind, I need to find a clever way of letting people smarter than me to do clever things with my constructs.
This is an open problem, I’d love to say I knew how to do this. The reality is, I will likely abstract the problem away into a class which breaks apart a larger chunk into smaller ones for processing, just like the TBB Range concept. It may be that I use that, but I’m not sure the interfaces are sufficient. People can then use this class in clever ways.
This brings me to the other subtopic in my blog title: dividing work. I need to provide users with a method by which they can determine which execution_graph gets what job, or at least dividing up a pile of work to do. I have a feeling that the solution to this problem will be incorporated with the former problem I stated, which is how to determine boundaries during processing.
That’s it for now, I’ll post another blog tomorrow night to keep you up to date with my design thoughts, and perhaps some code tidbits! Once things get more concrete, conversations should be a bit easier. It’s hard to convey my thoughts when I haven’t thought them all yet.
February 11th, 2008 at 2:08 am
Dependencies between adjacent data areas (such as in Conway’s Game of Life) are always a problem for multithreading. Some people from Aeshen used TBB to multithread a version of Conways’s Game of Life:
http://aeshen.typepad.com/aeshen/2007/02/the_game_of_lif.html
But I haven’t studied their code to see how they did it.
I’ve faced this problem of linkage between adjacent data elements, and typically I’ve looked for other means to multithread the software. One solution is to create larger data chunks that include the adjacent elements upon which each data area is dependent, then multithread the analysis of those larger chunks. But that can be quite cumbersome and use a lot of memory: you may be duplicating the same data element many times.
Also, coming up with a generic method for doing this seems difficult, since each data set may have different interlinking dependencies…