并行编程实战——TBB中的图

avatar
作者
筋斗云
阅读量:0

一、graph

在TBB框架中,基础的运行框架就是图graph。简单的回顾一下什么是图?图是由顶点和边组成的数学结构,表示对象及其之间的关系。图分为有向图和无向图。在TBB中,其实它的图叫做流图(Flow Graph),其实就可以理解成为一种有向图。流,一定不是静止的;一定是有方向的,也不可能乱流。
在流图中,分成节点(Node)和边(Edge)两种基本元素,可以把流图理解成节点和边的集合。整个TBB的运行其实就是通过对图特别是其内部的节点和边的操作来实现整体的计算任务的。
在图中有一个比较重要的问题需要说明,节点内的操作是异步的,任务是可并行的,但可并行并不意味着任务的立刻并行开始,它仍然需要底层线程的支持。并行的任务,只有在遇到可容纳的足量的线程时,才可能是真正并行的。

二、节点和边

在图中的节点有很多种类型,它通过会继承oneapi::tbb::flow::graph_node类,或者继承 oneapi::tbb::flow::sender 和 oneapi::tbb::flow::receiver。 但基本目的就是控制输入输出的端点数量或数据输入或输出的并行数量(这一点可以和Socket的端点进行类比)。
而边的功能很容易理解,就是把各个功能节点连接起来,实现数据的流动。这些边可以动态的修改如增、删。有了节点和边,就可以把数据与任务的流动整合起来,实现设计的目的。

三、图的应用

下面看一个官网上的图的应用的例子:

在这里插入图片描述

其相应的例程如下:

int sum = 0; graph g; function_node< int, int > squarer( g, unlimited, [](const int &v) {     return v*v; } ); function_node< int, int > cuber( g, unlimited, [](const int &v) {     return v*v*v; } ); function_node< int, int > summer( g, 1, [&](const int &v ) -> int {     return sum += v; } ); make_edge( squarer, summer ); make_edge( cuber, summer );  /× for ( int i = 1; i <= 10; ++i ) {   squarer.try_put(i);   cuber.try_put(i); }×/ //增加一个广播节点可以更好的体现TBB节点的流图 broadcast_node<int> b(g); make_edge( b, squarer ); make_edge( b, cuber ); for ( int i = 1; i <= 10; ++i ) {   b.try_put(i); } g.wait_for_all();   cout << "Sum is " << sum << "\n"; 

这是一个非常简单的图的应用,最左端的节点用来产生数据并传送给两个连接的计算功能节点,其中squarer用来计算平方值,并将结果继续传递给其后继的连接节点summer;同样,另外一个功能节点cuber接收到产生的数据后,将数据计算立方值并传递给summer。最后,由summer节点进行和计算并最终输出计算结果。
正如上面例程,可以直接向计算节点try_put数据,也可以增加广播节点;同样也可以使用input_node方式,而且这两种方式会更容易理解。但未必会节省代码,特别是使用输入节点后,反而会使程序变得更复杂。所以,一定要分清楚应用场景,而不能想怎么用就怎么用。

四、总结

TBB中图的使用是一个非常巧妙的设计,但是一定要明白图设计中的一些细节,特别是图连接状态与节点激活间先后以及是否保证数据的安全流动(如不丢失数据)。在前面分析的文章中就遇到了这类问题,所以一定要把TBB整体的流程和协调方式以及各种相关关系理清,才可能更大限度的发挥TBB的性能。

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!