ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

oneTBB Flow Graph 节点构造函数详解:用 follows / precedes 声明式构建依赖图

oneTBB Flow Graph 节点构造函数详解:用 follows / precedes 声明式构建依赖图 oneTBB Flow Graph 节点构造函数详解用 follows / precedes 声明式构建依赖图【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold本文聚焦于 vendored oneTBB 参考手册中的《Constructors for Flow Graph nodes》章节系统讲解如何借助follows与precedes辅助函数、以声明式方式构造 oneTBB Flow Graph 中的各类节点涵盖全部节点类型的构造函数签名、make_node_set配合用法、C17 CTAD 支持并结合仓库内 TBB 源码flow_graph.h、_flow_graph_node_set_impl.h剖析其底层实现原理与编译期约束。读完本文你将能写出不再显式传graph参数、边与节点一次性声明完成的流图构建代码并理解该特性的适用前提与限制。特性概述为表达图关系的辅助函数新增构造函数TBB_PREVIEW_FLOW_GRAPH_FEATURES预览特性为 oneTBB 流图新增了一组构造函数重载用于构造一个跟随follows或前置precedes一组节点的节点。在传统写法中每个节点构造时都需要显式传入其所属的graph随后再用make_edge逐条连接边而使用新构造函数后节点间的拓扑关系可以在构造节点的那一刻一并声明由库自动完成建边代码如下更接近对数据流拓扑的直接描述。需要特别说明的是该特性由预览宏控制默认处于关闭状态。要启用它必须在包含头文件之前定义#define TBB_PREVIEW_FLOW_GRAPH_FEATURES 1 #include oneapi/tbb/flow_graph.h从仓库源码 _config.h 可以看到TBB_PREVIEW_FLOW_GRAPH_FEATURES在未定义时会回退到__TBB_CPF_BUILD的取值而围绕它派生的开关还包括见 _config.h__TBB_PREVIEW_FLOW_GRAPH_NODE_SET—— 本文所述node_set/follows/precedes/make_node_set机制的总开关__TBB_PREVIEW_MESSAGE_BASED_KEY_MATCHING、__TBB_PREVIEW_FLOW_GRAPH_TRY_PUT_AND_WAIT、__TBB_PREVIEW_FLOW_GRAPH_RESOURCE_LIMITING—— 同组预览特性。在可能的情况下这些构造函数支持C17 的类模板参数推导CTAD, Class Template Argument Deduction即function_node、broadcast_node等模板节点的类型参数可以由实参自动推导进一步减少样板代码。核心语义follows 与 precedesfollows与precedes的完整语义定义在同目录的 follows_and_precedes_functions.rst 中要点如下follows(...)正在构造的节点是指定节点集合的后继successor即消息从集合中的节点流入新节点precedes(...)正在构造的节点是指定节点集合的前驱predecessor即消息从新节点流向集合中的节点这两个辅助函数只能用于节点构造期间用来取代原本作为构造器第一个参数传入的graph——新节点所属的图从传入的节点集合或节点序列中推导得出如果传入的节点分属不同的图graph行为是未定义的undefined使用时应确保节点来自同一个graph实例。从实现层面看_flow_graph_node_set_impl.h库内部用三个空标记类型表达集合的顺序状态namespace order { struct undefined {}; struct following {}; struct preceding {}; }follows与precedes各有两个重载一个接收若干节点实参变参序列另一个接收由make_node_set生成的节点集合。以follows为例_flow_graph_node_set_impl.htemplatetypename FirstPredecessor, typename... Predecessors node_setorder::following, FirstPredecessor, Predecessors... follows(FirstPredecessor first_predecessor, Predecessors... predecessors) { static_assert((conjunctionhas_output_typeFirstPredecessor, has_output_typePredecessors...::value), Not all nodes predecessors has output_type typedef); static_assert((conjunctionis_senderFirstPredecessor, is_senderPredecessors...::value), Not all nodes predecessors are senders); return node_setorder::following, FirstPredecessor, Predecessors...(first_predecessor, predecessors...); }这里有两个值得注意的编译期约束作为follows参数的每个节点都必须具有output_type类型别名否则会触发static_assert报错作为follows参数的每个节点都必须派生自senderT即都是发送方。对称地precedes要求参数节点具备input_type类型别名、并且派生自receiverT即都是接收方对应实现见 _flow_graph_node_set_impl.h。这些约束在编译期就把把错误类型的节点当作前驱/后继的问题拦截掉了。用 make_node_set 组装节点集合make_node_set函数模板用于创建一个节点集合对象该对象可作为make_edges、follows、precedes的参数make_node_set_function.rst#include oneapi/tbb/flow_graph.h template typename Node, typename... Nodes /*unspecified*/ make_node_set( Node node, Nodes... nodes );两种传参方式完全等价示例来自 follows_and_precedes_functions.rst// 方式一先组装集合再传给 precedes auto handlers make_node_set(n1, n2, n3); broadcast_nodeint input(precedes(handlers)); // 方式二直接把节点序列传给 precedes broadcast_nodeint input(precedes(n1, n2, n3));其实现_flow_graph_node_set_impl.h只是把传入的节点引用包装进一个以order::undefined为顺序标记的node_set后续交给follows/precedes时再转换为带方向的集合。支持新构造函数的节点类型总览原文档 constructors_for_nodes.rst 给出的完整构造函数语法如下涵盖 oneTBB 流图的大部分常用节点// continue_node continue_node(follows(...), Body body, Policy Policy()); continue_node(precedes(...), Body body, Policy Policy()); continue_node(follows(...), int number_of_predecessors, Body body, Policy Policy()); continue_node(precedes(...), int number_of_predecessors, Body body, Policy Policy()); // function_node function_node(follows(...), std::size_t concurrency, Policy Policy()); function_node(precedes(...), std::size_t concurrency, Policy Policy()); // input_node input_node(precedes(...), body); // multifunction_node multifunction_node(follows(...), std::size_t concurrency, Body body); multifunction_node(precedes(...), std::size_t concurrency, Body body); // async_node async_node(follows(...), std::size_t concurrency, Body body); async_node(precedes(...), std::size_t concurrency, Body body); // overwrite_node explicit overwrite_node(follows(...)); explicit overwrite_node(precedes(...)); // write_once_node explicit write_once_node(follows(...)); explicit write_once_node(precedes(...)); // buffer_node explicit buffer_node(follows(...)); explicit buffer_node(precedes(...)); // queue_node explicit queue_node(follows(...)); explicit queue_node(precedes(...)); // priority_queue_node explicit priority_queue_node(follows(...), const Compare comp Compare()); explicit priority_queue_node(precedes(...), const Compare compare Compare()); // sequencer_node sequencer_node(follows(...), const Sequencer s); sequencer_node(precedes(...), const Sequencer s); // limiter_node limiter_node(follows(...), std::size_t threshold); limiter_node(precedes(...), std::size_t threshold); // broadcast_node explicit broadcast_node(follows(...)); explicit broadcast_node(precedes(...)); // join_node explicit join_node(follows(...), Policy Policy()); explicit join_node(precedes(...), Policy Policy()); // split_node explicit split_node(follows(...)); explicit split_node(precedes(...)); // indexer_node indexer_node(follows(...)); indexer_node(precedes(...));使用要点上述follows(...)/precedes(...)占位符既可展开为follows(n1, n2, ...)的节点序列形式也可展开为follows(make_node_set(n1, n2, ...))的集合形式除构造参数本身外各节点原有的业务参数Body body、concurrency、threshold、Compare、Sequencer、Policy等位置与语义不变因此新构造函数是对旧构造函数的无损替代continue_node额外保留了int number_of_predecessors参数版本用于声明需要等待的前驱数量与普通continue_node(g, count, body, policy)一致标记为explicit的节点如overwrite_node、buffer_node、broadcast_node等禁止隐式转换构造时须显式写出类型。与传统写法的对比以continue_node为例。传统写法需要显式传入图对象并逐条调用make_edgegraph g; continue_nodecontinue_msg A(g, [](continue_msg) { /* ... */ }); continue_nodecontinue_msg B(g, [](continue_msg) { /* ... */ }); make_edge(A, B); // 手动建边新写法把属于哪个图与与谁相连统一在构造函数中声明graph g; continue_nodecontinue_msg A(g, [](continue_msg) { /* ... */ }); // B 跟随 AB 的图自动取自 A且 A-B 的边自动建立 continue_nodecontinue_msg B(follows(A), [](continue_msg) { /* ... */ });若使用 CTADC17continue_node的类型参数也可省略graph g; continue_node A(g, [](continue_msg) { return 0; }); // C17 CTAD continue_node B(follows(A), [](continue_msg) { return 0; });源码级原理图获取、多端口选择与自动建边新构造函数能够免传graph关键在于节点集合可以从成员节点推导出所属图。其实现链条清晰地位于 _flow_graph_node_set_impl.h 中。1. 从节点推导 graphnode_set内部用std::tupleNodes...持有各节点引用并通过graph_reference()暴露所属图_flow_graph_node_set_impl.htemplatetypename Order, typename... Nodes struct node_set { typedef Order order_type; std::tupleNodes... nodes; node_set(Nodes... ns) : nodes(ns...) {} 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_reference()取第一个节点作为图来源通过get_graph_helper实现_flow_graph_node_set_impl.h若节点派生自graph_node基类则直接取其内部my_graph成员否则回退到对象自身的graph_reference()方法。这也从实现上解释了文档中节点必须属于同一图否则行为未定义的约束——图信息只从首个节点取出跨图混用必然产生错误语义。在节点构造函数侧如 flow_graph.h 中continue_node的新构造函数所示它先通过nodes.graph_reference()解析出图再委托给传统构造函数完成初始化随后按集合方向自动建边continue_node( const node_setArgs... nodes, Body body, Policy p Policy(), node_priority_t a_priority no_priority ) : continue_node(nodes.graph_reference(), body, p, a_priority ) { ... }input_node同样只接受node_setorder::preceding, Successors...形式见 flow_graph.h与文档语法input_node(precedes(...), body)一一对应。2. 多端口节点的端口选择join_node、split_node、indexer_node等多端口节点拥有多个输入/输出端口直接对节点建边是不够的。库为此提供了端口选择器_flow_graph_node_set_impl.hsuccessor_selectorI::get(node)若节点具有input_ports_type多端口返回第I个input_portI(node)否则返回节点自身predecessor_selectorI::get(node)若节点具有output_ports_type多端口返回第I个output_portI(node)若节点是async_node则返回节点自身否则返回节点自身。这意味着precedes(n1, n2)中的n1、n2若为多端口节点将按参数顺序分别连接到新节点的第 0、1 号输出端口——顺序敏感使用时应保证参数顺序与端口编号对应。3. 自动建边make_edges_helper通过模板递归从第N-1号端口一路回连到第 0 号端口_flow_graph_node_set_impl.htemplatetypename PredecessorsTuple, typename NodeType static void connect_predecessors(PredecessorsTuple predecessors, NodeType node) { make_edge(std::getI(predecessors), successor_selectorI::get(node)); make_edges_helperI - 1::connect_predecessors(predecessors, node); }而make_edges_in_order则根据集合的order标记决定建边方向_flow_graph_node_set_impl.horder::following集合作为前驱make_edges(ns, node)order::preceding集合作为后继make_edges(node, ns)。整个方向标记 端口选择 递归建边的机制正是follows/precedes声明式语法能在构造时自动完成连线的原因。实战示例组合使用各节点类型下面构造一个具有代表性的流图input_node产出消息经broadcast_node扇出到两个分支其中一个分支经过function_node变换后与另一分支在join_node汇合。全程不显式传graph、不调用make_edge#define TBB_PREVIEW_FLOW_GRAPH_FEATURES 1 #include oneapi/tbb/flow_graph.h #include cstdio using namespace oneapi::tbb::flow; int main() { graph g; // 数据源precedes 声明它扇出的所有后继 input_nodeint source(precedes(bcast), - int { static int i 0; if (i 3) return i; fc.stop(); return 0; }); // 扇出节点follows 声明前驱sourceprecedes 声明两个后继 broadcast_nodeint bcast(follows(source), precedes(adder, joiner)); // 变换节点跟随 bcast function_nodeint, int adder(follows(bcast), unlimited, [](int v) { return v * 10; }); // 汇合节点跟随 bcast 与 adder 两个前驱 join_nodestd::tupleint, int joiner(follows(bcast, adder)); function_nodestd::tupleint, int sink(follows(joiner), unlimited, [](std::tupleint, int t) { std::printf((%d, %d)\n, std::get0(t), std::get1(t)); }); source.activate(); g.wait_for_all(); return 0; }注意bcast同时出现在follows与precedes中说明一个节点可以同时是某个集合的后继、另一个集合的前驱所有节点均属于同一个graph g满足同图前提。若需要把前驱/后继数量动态化或复用同一组节点多次可先用make_node_set组装集合再传入。限制与注意事项预览特性必须定义TBB_PREVIEW_FLOW_GRAPH_FEATURES 1且该宏同时控制同组的多个预览开关预览 API 在后续版本中可能调整。仅限构造期follows/precedes只能在节点构造时使用不能在运行时修改已有节点的连接关系。同图约束传入follows/precedes的所有节点必须属于同一graph否则行为未定义。编译期校验follows要求参数具备output_type且为senderprecedes要求参数具备input_type且为receiver违反者直接触发static_assert。端口顺序敏感对多端口节点join_node、split_node、indexer_node建边时按参数顺序映射端口async_node在前驱侧有专门处理。C 标准CTAD 特性需要 C17 或更高版本不使用 CTAD 时仍需显式写出模板参数。延伸阅读follows 与 precedes 函数模板语义定义与输入参数等价形式make_node_set 函数模板节点集合的组装方式与make_edges配合用法flow_graph.h各节点类的完整构造器与行为说明_flow_graph_node_set_impl.hnode_set、端口选择器与自动建边的完整实现。需要说明的是本仓库mold 链接器以 vendored 方式在 third-party/tbb 中携带了完整 oneTBB 源码及其文档mold 自身在 src/icf.cc、src/gdb-index.cc、src/cmdline.cc 等处主要使用 TBB 的并行算法与并发容器来加速链接过程而本文介绍的 Flow Graph 节点构造函数属于 TBB 流图模块的独立特性可在任何基于 oneTBB 的 C 项目中按上述方式直接使用。【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表