tbbcontrol-flow-graphtbb-flow-graph

What's the difference between data flow graph and dependence graph in TBB


I have read about data flow graph and dependence graph from the Intel TBB Tutorial, and feel a bit confusing about these two concepts.

Can I say that the key difference between data flow graph and dependence graph is whether there are explicitly shared resources or not?

But it seems that we can implement a dependence graph using function_node with pseudo messages, or implement a data flow graph using continue_node with shared global variables.


Solution

  • The difference between a function_node accepting a continue_msg input and a continue_node is the behavior when receiving a message. This is a consequence of the concept of "dependence graph."

    The idea of dependence graphs is that the only information being passed by the graph is the completion of a task. If you have four tasks (A,B,C,D) all operating on the same shared data, and tasks A and B must be complete before either C or D can be started, you define four continue_nodes, and attach the output of node A to C and D, and the same for B. You may also create a broadcast_node<continue_msg> and attach A and B as successors to it. (The data being used in the computation must be accessible by some other means.)

    example dependence graph

    To start the graph you do a try_put of a continue_msg to the broadcast_node. The broadcast_node sends a continue_msg to each successor (A & B).

    continue_nodes A and B each have 1 predecessor (the broadcast_node.) On receiving a number of continue_msgs equal to their predecessor count (1), they are queued to execute, using and updating the data representing the state of the computation.

    When continue_node A completes, it sends a continue_msg to each successor, C & D. Those nodes each have two predecessors, so they do not execute on receiving this message. They only remember they have received one message.

    When continue_node B completes, it also sends a continue_msg to C and D. This will be the second continue_msg each node receives, so tasks will be queued to execute their function_bodies.

    continue_nodes use the graph only to express this order. No data is transferred from node to node (beyond the signal that a predecessor is complete.)

    If the nodes in the graph were function_nodes accepting continue_msgs rather than continue_nodes, the reaction to the broadcast_node getting a continue_msg would be

    1. The broadcast_node would forward a continue_msg to A and B, and they would each execute their function_bodies.
    2. Node A would complete, and pass continue_msgs to C and D.
    3. On receiving the continue_msg, tasks would be queued to execute the function_bodies of C and D.
    4. Node B would complete execution, and forward a continue_msg to C and D.
    5. C and D, on receiving this second continue_msg, would queue a task to execute their function_bodies a second time.

    Notice 3. above. The function_node reacts each time it receives a continue_msg. The continue_node knows how many predecessors it has, and only reacts when it receives a number of continue_msgs equal to its number of predecessors.

    The dependence graph is convenient if there is a lot of state being used in the computation, and if the sequence of tasks is well understood. The idea of "shared state" does not necessarily dictate the use of a dependence graph, but a dependence graph cannot pass anything but completion state of the work involved, and so must use shared state to communicate other data.

    (Note that the completion order I am describing above is only one possible ordering. Node B could complete before node A, but the sequence of actions would be similar.)