资讯详情

资讯详情

oneTBB `make_node_set` 函数模板:用节点集合简化 Flow Graph 连边

oneTBBmake_node_set函数模板用节点集合简化 Flow Graph 连边【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold导读在 oneTBB 的 flow graph 编程模型中make_node_set是“Helper Functions for Expressing Graphs”图形表达辅助函数这套预览特性Preview API的核心构件之一。它把一个或多个流图节点收集成一个可复用的“节点集合”再交给make_edges、follows、precedes以及配套的节点构造函数批量建立边关系从而把多条make_edge调用压缩成一条语句。本文以 make_node_set_function.rst 为骨架结合 oneTBB 头文件 _flow_graph_node_set_impl.h、RFC 文档 flow_graph_node_sets/README.md、官方示例 make_edges_function_example.cpp 与 helpers_for_expressing_graphs_preview_api_example.cpp 展开读完你将掌握何时需要节点集合、如何启用预览特性、make_node_set的语法与底层类型、以及它与make_edges/follows/precedes和新增节点构造函数的组合用法与注意事项。一、背景为什么需要“节点集合”生产版非预览的 flow graph API 只提供make_edge(a, b)这种一次连接一条边的原语。当一个节点需要连接多个后继或前驱时代码会变得冗长。RFC 文档 README.md 中给出了一个典型场景input节点有三个后继doubler、squarer、cuber传统写法需要三次make_edgebroadcast_nodeint input(g); function_node doubler(g, unlimited, [](const int v) { return 2 * v; }); function_node squarer(g, unlimited, [](const int v) { return v * v; }); function_node cuber(g, unlimited, [](const int v) { return v * v * v; }); make_edge(input, doubler); make_edge(input, squarer); make_edge(input, cuber);这种“一对多”的连边模式在图构建中非常常见扇出/扇入、流水线合并等逐条调用make_edge不仅冗长还降低了可读性。为此oneTBB 在预览特性中引入了四个相互配合的构件make_node_set函数模板 —— 收集节点成集合make_edges函数模板 —— 在单个节点与节点集合之间批量建边follows与precedes函数模板 —— 表达“正在构造的节点”与节点集合之间的顺序关系部分而非全部flow graph 节点类型新增的构造函数 —— 直接用follows(...)/precedes(...)替换构造函数的 graph 参数。make_node_set正是这套体系中的“数据容器”先把节点归拢成集后续所有批量连边操作都以它为输入。二、启用预览特性make_node_set属于预览 API。原文档 make_node_set_function.rst 明确要求要启用该特性必须把TBB_PREVIEW_FLOW_GRAPH_FEATURES宏定义为 1。在源码层面_flow_graph_node_set_impl.h 中整个实现都包在#if __TBB_PREVIEW_FLOW_GRAPH_NODE_SET预处理器分支内只有定义了该宏才会被编译进去。启用方式有两种参考官方示例 make_edges_function_example.cpp// 方式一在包含头文件之前定义宏 #define TBB_PREVIEW_FLOW_GRAPH_FEATURES 1 #include oneapi/tbb/flow_graph.h // 方式二编译时通过 -D 传入等价 // g -stdc17 -DTBB_PREVIEW_FLOW_GRAPH_FEATURES1 ...两点注意事项必须在使用任何 flow graph API 之前定义该宏且所有翻译单元要保持一致否则可能出现声明/定义不匹配官方示例均以#if __cplusplus 201703L作为前置条件见 make_edges_function_example.cpp这是因为 C17 的类模板实参推导CTAD是这套 API 精简写法的前提。三、make_node_set的声明与语义3.1 头文件与语法原文档给出的 API 如下#include oneapi/tbb/flow_graph.h template typename Node, typename... Nodes /*unspecified*/ make_node_set( Node node, Nodes... nodes );也就是说make_node_set接收至少一个节点的左值引用返回一个“未指定”类型。这个“未指定”类型在 RFC 中是 exposition-only仅用于说明的名字即node_set由make_node_set创建的对象其第一个模板实参是order::undefined。3.2 底层实现node_set 结构体从 _flow_graph_node_set_impl.h 可以看到实际的类型定义namespace order { struct undefined {}; struct following {}; struct preceding {}; } templatetypename Order, typename... Nodes struct node_set { typedef Order order_type; std::tupleNodes... nodes; // 以引用元组保存所有节点 node_set(Nodes... ns) : nodes(ns...) {} // 允许从 order::undefined 的集合构造供 follows/precedes 复用 template typename... Nodes2 node_set(const node_setorder::undefined, Nodes2... set) : nodes(set.nodes) {} graph graph_reference() const { return get_graph_helper::get(std::get0(nodes)); // 取第一个节点所属的 graph } }; template typename Node, typename... Nodes node_setorder::undefined, Node, Nodes... make_node_set(Node first_node, Nodes... nodes) { return node_setorder::undefined, Node, Nodes...(first_node, nodes...); }关键设计点引用语义node_set内部用std::tupleNodes...保存节点的左值引用不拷贝节点本身顺序标签order::undefined/order::following/order::preceding三个 tag 结构体记录集合的“顺序类型”。make_node_set产生的是order::undefined后续follows会把集合转换为order::followingprecedes转换为order::precedinggraph 归属graph_reference()通过get_graph_helper取集合中第一个节点所属的 graph。若node_set中的节点来自不同 graph行为未定义。四、与make_edges配合批量建边4.1 语法与方向语义make_edges_function.rst 给出了两个重载template typename NodeType, typename Node, typename... Nodes void make_edges(node_setNode, Nodes... set, NodeType node); template typename NodeType, typename Node, typename... Nodes void make_edges(NodeType node, node_setNode, Nodes... set);参数顺序即方向左边实参代表的节点成为右边实参代表节点的前驱predecessor右边实参成为后继successor。底层实现make_edges_helper会递归地对std::tuple中的每个节点调用make_edge并通过successor_selector/predecessor_selector处理带多端口节点的端口选择。4.2 完整可运行示例官方示例 make_edges_function_example.cpp 演示了“一个广播节点 → 三个处理节点 → 一个缓冲节点”的完整图#define TBB_PREVIEW_FLOW_GRAPH_FEATURES 1 #include oneapi/tbb/flow_graph.h int main() { using namespace oneapi::tbb::flow; graph g; broadcast_nodeint input(g); function_node doubler(g, unlimited, [](const int i) { return 2 * i; }); function_node squarer(g, unlimited, [](const int i) { return i * i; }); function_node cuber(g, unlimited, [](const int i) { return i * i * i; }); buffer_nodeint buffer(g); auto handlers make_node_set(doubler, squarer, cuber); make_edges(input, handlers); // input 是三个节点的前驱 make_edges(handlers, buffer); // 三个节点是 buffer 的前驱 for (int i 1; i 10; i) { input.try_put(i); } g.wait_for_all(); }这里make_edges(input, handlers)等价于三条make_edge(input, doubler)、make_edge(input, squarer)、make_edge(input, cuber)而make_edges(handlers, buffer)则把三个处理节点的输出汇聚到buffer。通过make_node_set把节点先收集成集再用两条make_edges就完成了整个扇出-汇聚拓扑的构建。4.3 与旧式写法的对比RFC 文档 README.md 中列出了make_edges的完整签名含OrderFlagType模板参数语义与上述一致左参数 → 右参数决定前驱/后继。它与构造函数的本质区别在于make_edges的前驱/后继关系由实参的相对位置推断新增构造函数的关系由order_typefollowing/preceding显式决定。五、与follows/precedes配合在构造时建边5.1 语义follows_and_precedes_functions.rst 定义了这两个辅助函数follows(...)正在构造的节点是参数集合的后继参数集合成为它的前驱precedes(...)正在构造的节点是参数集合的前驱参数集合成为它的后继。它们用于替换节点构造函数的第一个 graph 参数——图引用从传入的节点集合中获取调用node_set::graph_reference()。若传入的节点分属不同 graph行为未定义。一个容易混淆的点RFC 明确提出的设计讨论order::preceding集合里的节点最终是后继而非前驱因为“preceding”描述的是尚未构造的节点 N相对集合中节点的顺序——N 在这些节点之前preceding所以集合节点是 N 的后继反之order::following集合里的节点是 N 的前驱。命名是站在 N 的视角而非集合自身的视角。5.2 两种等价用法follows/precedes既可以接收node_set也可以直接接收一串节点原文档 follows_and_precedes_functions.rst 明确两者等价// 用法一先 make_node_set再 follows/precedes 套一层 auto handlers make_node_set(n1, n2, n3); broadcast_nodeint input(precedes(handlers)); // 用法二直接传节点序列 broadcast_nodeint input(precedes(n1, n2, n3));底层上precedes(handlers)通过 _flow_graph_node_set_impl.h 中针对node_setorder::undefined, ...的重载把order::undefined集合转换为order::preceding集合并附带static_assert检查集合内所有节点都提供input_type且是receiveris_receiverfollows同理要求所有节点提供output_type且是sender见 第 106-125 行。5.3 结合构造函数使用的完整示例官方预览示例 helpers_for_expressing_graphs_preview_api_example.cpp 展示了一个“扇出 → 合并 → 求和”的完整图全部用节点集合完成建边且大量借助 C17 CTAD 省略模板参数#define TBB_PREVIEW_FLOW_GRAPH_FEATURES 1 #include oneapi/tbb/flow_graph.h int main() { using namespace oneapi::tbb::flow; graph g; function_node doubler(g, unlimited, [](const int v) { return 2 * v; }); function_node squarer(g, unlimited, [](const int v) { return v * v; }); function_node cuber(g, unlimited, [](const int v) { return v * v * v; }); auto handlers make_node_set(doubler, squarer, cuber); broadcast_node input(precedes(handlers)); // 扇出input → 三个节点 join_node join(follows(handlers)); // 合并三个节点 → join int sum 0; function_node summer(follows(join), serial, { int sub_sum std::get0(v) std::get1(v) std::get2(v); sum sub_sum; return sub_sum; }); for (int i 1; i 10; i) { input.try_put(i); } g.wait_for_all(); }注意broadcast_node input(precedes(handlers))中input的模板实参int由 CTAD 从handlers节点的input_type推导得出详见下节“推导指南”不再需要手写int。这正是节点集合特性带来的额外价值它不仅减少了连边代码还让节点类型可以从邻居节点自动推导。六、node_set支持的节点类型与特殊规则6.1 单输入单输出节点多数常规节点可以接收following或preceding两种集合包括continue_node、function_node、async_node、overwrite_node、write_once_node、buffer_node、queue_node、priority_queue_node、sequencer_node、limiter_node、broadcast_node。构造函数完整签名见 constructors_for_nodes.rst 和 RFC README.md例如// function_node用 follows/precedes 集合替换 graph 参数 function_node(follows(...), std::size_t concurrency, Body body, Policy Policy()); function_node(precedes(...), std::size_t concurrency, Body body, Policy Policy()); // broadcast_node / buffer_node 等存储类节点 explicit broadcast_node(follows(...)); explicit broadcast_node(precedes(...));6.2 特殊节点类型RFC README.md 详细说明了三类特殊情况无输入、单输出input_node自身没有输入端口只能接收order::preceding集合即只能“precedes”其后继不能follows单输入、多输出multifunction_node、split_node接收following集合时按常规规则把集合节点连到单一输入端口接收preceding集合时要求集合大小恰好等于输出端口数并按索引一一对应建边即output_port0 → 集合第 0 个节点、output_port1 → 集合第 1 个节点……RFC 中的可运行示例第 343-385 行展示了tbb::flow::precedes(n2, n3)如何让multifunction_node的两个输出端口分别连到 n2、n3多输入、单输出join_node、indexer_node与上一条对称。接收preceding集合时按常规规则连到单一输出端口接收following集合时集合大小必须等于输入端口数按索引一一对应建边。此外 RFC 明确指出一个构造函数要么接收前驱集合要么接收后继集合不能同时接收两者目前不支持composite_node的节点集合构造函数。七、类模板实参推导CTAD指南节点集合带来的另一个亮点是让 C17 的 CTAD 可以从邻居节点的类型推导新节点的类型。RFC README.md 中给出了decide_on_set辅助 traitorder::following集合取集合中第一个节点的output_typeorder::preceding集合取集合中第一个节点的input_type。基于此overwrite_node、write_once_node、broadcast_node、buffer_node、queue_node、priority_queue_node、limiter_node都新增了显式推导指南例如template typename NodeSet broadcast_node(const NodeSet) -broadcast_nodedecide_on_set_tNodeSet;对多输入节点join_node、indexer_nodefollowing集合按“每个输入端口一个节点”的规则推导出std::tuple类型对join_node的preceding集合则直接从集合第一个节点的input_type推导。多输出节点中只有split_node支持基于节点集合的推导multifunction_node和async_node目前没有对应的显式推导指南。这些正是上一节示例中broadcast_node input(precedes(handlers))能够省略int的原因。八、测试佐证节点集合特性在测试代码中同样有覆盖例如 test_join_node_multiple_predecessors.h 中的connect_join_via_follows与connect_join_via_precedes两个辅助函数分别通过make_node_set(bn1, bn2, bn3)follows(bn_set)和make_node_set(qn)precedes(qn_copy_set)来构造join_node验证了“集合 follows/precedes”这条路径在真实图构建中的可用性。另一处 test_flow_graph_whitebox.cpp 也直接使用make_node_set可作为深入阅读的参考。九、使用注意事项小结必须启用预览宏使用前#define TBB_PREVIEW_FLOW_GRAPH_FEATURES 1或编译期-D且需先于#include oneapi/tbb/flow_graph.h节点必须属于同一个 graphnode_set通过第一个节点的graph_reference()获取图引用跨图混用属于未定义行为make_edges的方向由参数顺序决定而构造函数的连接关系由集合的order_type决定多端口节点的特殊规则preceding集合配多输出节点、following集合配多输入节点时集合大小必须与端口数一致并按索引连接input_node只能 precedes不能 followscomposite_node暂不支持前置条件 C17CTAD 依赖 C17官方示例均以#if __cplusplus 201703L保护预览 API 的接口可能随 oneTBB 版本演进调整生产环境使用前请关注其正式化进度。参考资源官方参考文档make_node_set_function.rst、make_edges_function.rst、follows_and_precedes_functions.rst、constructors_for_nodes.rst、helpers_for_expressing_graphs.rst实现源码_flow_graph_node_set_impl.h实验性设计 RFCflow_graph_node_sets/README.md可运行示例make_edges_function_example.cpp、helpers_for_expressing_graphs_preview_api_example.cpp测试佐证test_join_node_multiple_predecessors.h、test_flow_graph_whitebox.cpp【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →