【问题标题】:join_node graph flow constructionjoin_node 图流构建
【发布时间】:2017-07-20 15:10:08
【问题描述】:

我正在试验来自 TBB 的 Intel Graph Flow。我对结果非常满意,我发现产品很棒,有无限的可能性。然而,我遇到了一个我修复的 pb,但我并不满意。 pb如下。

   message
A ----------\       tuple<message,message>             WHATEVER
   message   join ------------------------- C------------------------
B ----------/

当我们想要同步并避免传播 n 次消息(及其值)时,应用此模式。英特尔提供了一个很好地解释了 pb 的示例(以及解决方案 - Intel example)。我的 pb 是使用静态方法构造的元组和图的构造。它是完全静态的,特别是如果连接节点的输入边数(英特尔示例中为 input_port&lt;i&gt;)是变量。

TBB-graph flow 的专家知道这个 pb 的“动态方法”吗?

最好的,

蒂姆 [编辑我的代码真实 pb]

我能做到:

std::vector<tbb::flow::function_node<std::size_t, message>> vec_node;

for (int i(0) ; i < 3 ;++i)
        nodes_cont_.emplace_back(my_amazing_function_to_create_node(g_));

tbb::flow::make_edge(vec_node[0], tbb::flow::input_port<0>

tbb::flow::make_edge(vec_node[1], tbb::flow::input_port<1>(node_join_));
tbb::flow::make_edge(vec_node[2], tbb::flow::input_port<2>(node_join_));

我做不到:

for(int i(0); i < vec_node.size(); ++i)
    tbb::flow::make_edge(vec_node[i], tbb::flow::input_port<i>(node_join_));

由于“元组”和“tbb::flow::input_port”函数。

【问题讨论】:

  • 嗨蒂姆,我是否理解正确,您想在运行时动态地将边添加到 join_node 并可能改变必须加入的消息数量?另外,键匹配 join_node 如何帮助您解决问题?它旨在组合精确的关键消息,但输入端口的元组仍然是静态的。
  • 能否请您提供使用 join_node 的 FG 使用的真实示例?
  • 我用真实的pb更新了这个问题,希望它会有所帮助。感谢您的关注

标签: tbb tbb-flow-graph


【解决方案1】:

join 节点上的端口数是静态的(在编译时确定。)如果您想要一个输出的不同输入数,您需要能够指示消息来自哪个“端口” ,以及它的价值。

TBB 有一个变体类型,它封装了一个端口号和一个值(它是indexer_node 的输出。)如果你使用那个类型(定义一个indexer_node,但不要实例化它,你可以使用节点的::output_type)作为multifunction_node 的输入类型(可能有多个输出,但可以只有一个输出),并让multifunction_node 的函数体决定它何时具有具有正确数量的输出,然后您可以在输入值时存储它们,并且当multifunction_node 看到“正确”数量的输入时,它可以构造一个输出值并将其转发给它的后继者。

图表将如下所示:

我看到的一个问题是您必须定义multifunction_node 的输出类型。这也是一个静态声明,尽管您可能需要一个变体元组。

编辑:

让我们做一些简化的假设:

  • 虽然N 在编译时未知,但它在运行时是已知且不变的。放宽此约束将需要在每条消息中传递一些额外的数据。
  • 虽然您使用的是tuple,但我相信这是因为join_nodes 的输出是tuple(我尝试添加向量作为特殊情况,但我认为这不是一个选项。 ) 我假设向量中的所有function_nodes 都与输出具有相同的类型。这样我们就可以避免使用变体类型。
  • 我们传递的数据不是特别大(复制构造不是特别昂贵)。放宽这个约束将需要在访问每个节点中的数据时更加小心。
  • 有些东西可以唯一地定义一起出现的消息。例如,如果您将一个只读数据缓冲区交给向量中的每个 function_node,则该缓冲区的地址就是让我们知道将哪些消息放在一起的部分。

我从事TBB工作已经有几年了,所以可能有些事情我不知道,但我可以给你一个草图。

图表将如下所示:

(我实际上是在勾勒出标签匹配连接的结构,因为听起来这就是你想要的。)

在构造function_nodes 的向量时,每个function_body 都必须知道它的索引是什么。一般来说,这意味着向量是指向function_nodes 的指针,每个节点都是以索引作为其参数之一构造的。

我假设source_node's 输出类似于缓冲区。该缓冲区被传递给向量中的每个function_node,每个function_node 的输出类型为

  • 缓冲区地址
  • function_node 在该节点向量中的索引
  • 其他神奇的好处

multifuncton_node 是完成大部分工作的地方。它有

  • hash_maps 的向量,由 function_node 索引索引,并带有缓冲区地址的键,包含各个缓冲区的每个 function_node 的结果。
  • 带有缓冲区地址键的hash_map,包含为该缓冲区接收到的元素的数量。当它达到 N 时,您就拥有了所有的输入。

multifunction_node收到一条消息时,它

  • 将数据添加到hash_map[i][key],其中i是function_node索引(在输入消息中),key是缓冲区地址
  • 增加hash_count[key]。如果现在是N,那么
  • 构造结果值的向量,将每个结果值从该索引的哈希表中提取出来。
  • 如果您构造了该值,则转发该值,否则返回。

对于数据如何传递和存储,以及如果您希望值被重用,如何清理元素存在一些问题,但这是基本草图。

【讨论】:

  • 好的,我需要学习、理解和编写代码。然后我回来。我更新了我的例子
  • 加油!非常感谢!
  • np。不要忘记节点将异步运行,因此如果您正在修改共享数据,则必须序列化这些访问。您可以使用 smart_pointers 传递数据,但这仅处理释放未使用的数据,而不是序列化访问。祝你好运。
  • 您也可以创建一个function_nodes的向量,其容量设置为N,然后使用索引构造的function_bodies附加节点。但是当函数体被引用和通过引用传递时,我总是感到困惑,所以我一直使用向量中的指针。
  • 经过两个月的头疼后的小更新,我们能够使用多功能节点创建一个“动态”连接节点。我们的图表更加动态,非常酷。感谢您的支持
【解决方案2】:

如果您在编译时知道特定程序的 N,但希望以通用方式实现图形以使库在不同程序中使用,则 BOOST_PP 是小 N 的一个选项。

我已经实现了一个图表,它在最慢的连接节点输出 continue_msg 后生成 continue_msg。为此,我需要 N 个缓冲区节点,并将它们连接到具有 N 个相同类型 (tbb::flow::continue_msg) 端口的连接节点。

基本上,下面的代码符合您的预期

for(int i(0); i < vec_node.size(); ++i)
    tbb::flow::make_edge(vec_node[i], tbb::flow::input_port<i>(node_join_));

...但是使用预编译器通过正确的 make_edge 调用“写入”多行,但最多只能达到 N(对于 N

    #include "boost/preprocessor/repetition/repeat_from_to.hpp"
    #include "boost/preprocessor/repetition/repeat.hpp"
    #include "boost/preprocessor/arithmetic/inc.hpp"

    ...

    #define MY_JOIN_NODE_VARIADIC_MAX 8
    #define MY_FUNC_IMPL(z, n, unused) tbb::flow::make_edge(vec_node[##n], tbb::flow::input_port<##n>(joinNode));
    #define MY_MAKE_IMPL(z, n, unused)                                  \
    template <size_t N, typename TJoinNode> void                        \
    makeAllEdges (TJoinNode& joinNode,                                  \
                     typename std::enable_if< N == n >::type * = 0)     \
    {                                                                   \
        BOOST_PP_REPEAT(n, MY_FUNC_IMPL, unused)                          \
    }
    BOOST_PP_REPEAT_FROM_TO(0, BOOST_PP_INC(MY_JOIN_NODE_VARIADIC_MAX), MY_MAKE_IMPL, unused)
    #undef MY_MAKE_IMPL
    #undef MY_FUNC_IMPL
    #undef MY_JOIN_NODE_VARIADIC_MAX

这段代码是一个函数定义。然后可以调用“makeAllEdges”。 (请注意,在本例中,我假设 makeAllEdges 是一个类方法,而 vec_node 是该类的成员,因此在 makeAllEdges 的范围内是已知的。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-04-06
    • 1970-01-01
    • 1970-01-01
    • 2019-12-18
    • 1970-01-01
    • 1970-01-01
    • 2015-03-03
    • 1970-01-01
    相关资源
    最近更新 更多