Flow Graph Basics: Single-push vs. Broadcast-push

Flow Graph Basics: Single-push vs. Broadcast-push#

Nodes in the oneAPI Threading Building Blocks (oneTBB) flow graph communicate by pushing and pulling messages. Two policies for pushing messages are used, depending on the type of the node:

  • single-push: No matter how many successors to the node exist and are able to accept a message, each message will be only sent to one successor.

  • broadcast-push: A message will be pushed to every successor which is connected to the node by an edge in push mode, and which accepts the message.

The following code demonstrates this difference:

using namespace oneapi::tbb::flow;


std::atomic<size_t> g_cnt;


struct fn_body1 {
    std::atomic<size_t> &body_cnt;
    fn_body1(std::atomic<size_t> &b_cnt) : body_cnt(b_cnt) {}
    continue_msg operator()( continue_msg /*dont_care*/) {
        ++g_cnt;
        ++body_cnt;
        return continue_msg();
    }
};


void run_example1() {  // example for Flow_Graph_Single_Vs_Broadcast.xml
    graph g;
    std::atomic<size_t> b1;  // local counts
    std::atomic<size_t> b2;  // for each function _node body
    std::atomic<size_t> b3;  //
    function_node<continue_msg> f1(g,serial,fn_body1(b1));
    function_node<continue_msg> f2(g,serial,fn_body1(b2));
    function_node<continue_msg> f3(g,serial,fn_body1(b3));
    buffer_node<continue_msg> buf1(g);
    //
    // single-push policy
    //
    g_cnt = b1 = b2 = b3 = 0;
    make_edge(buf1,f1);
    make_edge(buf1,f2);
    make_edge(buf1,f3);
    buf1.try_put(continue_msg());
    buf1.try_put(continue_msg());
    buf1.try_put(continue_msg());
    g.wait_for_all();
    printf( "after single-push test, g_cnt == %d, b1==%d, b2==%d, b3==%d\n", (int)g_cnt, (int)b1, (int)b2, (int)b3);
    remove_edge(buf1,f1);
    remove_edge(buf1,f2);
    remove_edge(buf1,f3);
    //
    // broadcast-push policy
    //
    broadcast_node<continue_msg> bn(g);
    g_cnt = b1 = b2 = b3 = 0;
    make_edge(bn,f1);
    make_edge(bn,f2);
    make_edge(bn,f3);
    bn.try_put(continue_msg());
    bn.try_put(continue_msg());
    bn.try_put(continue_msg());
    g.wait_for_all();
    printf( "after broadcast-push test, g_cnt == %d, b1==%d, b2==%d, b3==%d\n", (int)g_cnt, (int)b1, (int)b2, (int)b3);
}

The output of this code is

after single-push test, g_cnt == 3, b1==3, b2==0, b3==0
after broadcast-push test, g_cnt == 9, b1==3, b2==3, b3==3

The single-push test uses a buffer_node, which has a “single-push” policy for forwarding messages. Putting three messages to the buffer_node results in three messages being pushed. Notice also only the first function_node is sent to; in general there is no policy for which node is pushed to if more than one successor can accept.

The broadcast-push test uses a broadcast_node, which will push any message it receives to all accepting successors. Putting three messages to the broadcast_node results in a total of nine messages pushed to the function_nodes.

Only nodes designed to buffer (hold and forward received messages) have a “single-push” policy; all other nodes have a “broadcast-push” policy.

Please see the Sending to One or Multiple Successors section of Flow Graph Tips and Tricks, and Flow Graph Basics: Buffering and Forwarding for more information.