ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

oneTBB flow_graph 的 join_node 类型指定消息键(Type-specified Message Keys)扩展详解

oneTBB flow_graph 的 join_node 类型指定消息键(Type-specified Message Keys)扩展详解 oneTBB flow_graph 的 join_node 类型指定消息键Type-specified Message Keys扩展详解【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold导读本文深入解析 oneTBB 流图flow_graph中join_node的类型指定消息键Type-specified message keys扩展它允许基于key_matching策略的join_node直接从消息类型自身获取匹配键从而省去为每个输入端口逐一编写函数对象functor的繁琐工作。本文将以 oneTBB 随仓库携带的源码与测试为证据完整覆盖该预览特性的启用方式、join_node(graph g)新构造函数、默认key_from_message实现、基于 ADL 的自定义扩展以及底层模板实现原理帮助读者在真实项目中安全、正确地使用这一特性。预览特性提示要启用该功能需要在包含头文件之前定义宏TBB_PREVIEW_FLOW_GRAPH_FEATURES并将其值设为 1见 type_specified_message_keys.rst。背景传统 key_matching join_node 的痛点join_node是 flow_graph 中用于多输入汇聚的关键节点。当使用key_matching策略时join_node会按照键值把来自不同输入端口、携带相同键的消息配对成元组后转发到输出。传统用法要求为每一个输入端口显式提供一个函数对象把该端口上的输入消息类型映射为键类型。例如在 flow_graph_msg_keys 实验特性说明 中给出的经典示例两个queue_nodeq0、q1连接到键匹配join_node jj再连接到输出queue_node q3。消息类型Message定义如下namespace msg { templatetypename K struct Message { K k; int v; }; template typename K std::ostream operator(std::ostream os, const MessageK m) { os ( m.k , m.v ); return os; } }传统方式下join_node的构造必须为每个输入端口分别传入取键 lambdatbb::flow::join_nodestd::tuplemsg_t, msg_t, tbb::flow::key_matchingint j{g, [](const msg_t m) { return m.k; }, [](const msg_t m) { return m.k; } };可以看到两个端口的取键逻辑完全相同却要重复书写两份。正如 RFC 文档所指出的如果join_node有 10 个输入端口就得传入 10 个函数对象README.md。这种重复不仅冗长还容易在端口数量变化时遗漏或写错。扩展目标把取键函数与消息类型绑定类型指定消息键扩展的核心思路是不再为每个端口手工指定取键函数而是把“如何从消息中取出键”这一职责绑定到消息类型本身上。这样无论join_node有多少个输入端口只要所有输入类型都能提供键构造时就无需再传入任何函数对象。实现这一思路的机制有两条路径二者选其一即可在消息类中定义key()成员函数由扩展自带的默认key_from_message实现自动调用在消息类型所在的命名空间内重载key_from_message自由函数通过 C 的参数依赖查找ADLArgument-Dependent Lookup被自动发现并使用。启用方式与 API 形态启用宏该特性当前是预览preview特性编译前必须显式启用#define TBB_PREVIEW_FLOW_GRAPH_FEATURES 1 #include oneapi/tbb/flow_graph.h头文件与普通 flow_graph 用法相同均为oneapi/tbb/flow_graph.h。仓库中的预览测试 test_join_node_msg_key_matching.cpp 正是在包含头文件之前定义了该宏。新构造函数扩展在key_matchingtypename K, class KHash tbb_hash_compare策略下为join_node新增了一个特殊构造函数type_specified_message_keys.rstjoin_node( graph g )也就是说当使用key_matching策略、且输入消息类型满足取键要求时可以只传图对象g完成构造无需再为每个端口提供取键函数对象。默认 key_from_message 实现当以join_node(graph g)方式构造时join_node会对每个到达的消息调用key_from_message函数来取得与之关联的键。默认实现位于oneapi::tbb::flow命名空间namespace oneapi { namespace tbb { namespace flow { template typename K, typename T K key_from_message( const T t ) { return t.key(); } } } }其中T是用户为join_node的OutputTuple提供的输入类型之一K是节点的键类型。默认情况下key_from_message会调用消息类中的key()成员方法。该默认实现可以在源码 flow_graph.h 中直接找到它被__TBB_PREVIEW_MESSAGE_BASED_KEY_MATCHING宏保护#if __TBB_PREVIEW_MESSAGE_BASED_KEY_MATCHING template typename K, typename T K key_from_message( const T t ) { return t.key(); } #endif /* __TBB_PREVIEW_MESSAGE_BASED_KEY_MATCHING */通过 ADL 自定义取键逻辑如果不想或不能为消息类添加key()成员函数用户可以在消息类型所在的同一个命名空间中定义自己的key_from_message重载。该函数会通过 C 参数依赖查找ADL被发现并替换默认实现namespace msg { template typename K K key_from_message(const MessageK m) { return m.k; } }由于该自由函数与消息类型MessageK处于同一命名空间msgADL 机制会保证在join_node内部对key_from_messageK(t)的调用能正确解析到用户版本。完整示例两种简化写法下面是从 RFC 实验文档 中整理出的完整可运行示例。它通过两个宏展示了两种简化路径定义USE_FUNCTION时为Message增加key()成员函数走默认key_from_message路径定义USE_ADL时在msg命名空间提供key_from_message重载走 ADL 路径两者都未定义时则回退到传统写法每个端口传 lambda。#define TBB_PREVIEW_FLOW_GRAPH_FEATURES 1 #include oneapi/tbb/flow_graph.h #include iostream namespace msg { templatetypename K struct Message { K k; int v; #if USE_FUNCTION K key() const { std::cout called key()\n; return k; } #endif }; template typename K std::ostream operator(std::ostream os, const MessageK m) { os ( m.k , m.v ); return os; } #if USE_ADL template typename K K key_from_message(const MessageK m) { std::cout used ADL\n; return m.k; } #endif } int main(int argc, char *argv[]) { using msg_t msg::Messageint; tbb::flow::graph g; tbb::flow::queue_nodemsg_t q0{g}; tbb::flow::queue_nodemsg_t q1{g}; #if USE_FUNCTION || USE_ADL // 简化写法无需为每个端口提供取键函数 tbb::flow::join_nodestd::tuplemsg_t, msg_t, tbb::flow::key_matchingint j{g}; #else // 传统写法每个端口一个取键 lambda tbb::flow::join_nodestd::tuplemsg_t, msg_t, tbb::flow::key_matchingint j{g, [](const msg_t m) { return m.k; }, [](const msg_t m) { return m.k; } }; #endif tbb::flow::queue_nodestd::tuplemsg_t,msg_t q3{g}; tbb::flow::make_edge(q0, tbb::flow::input_port0(j)); tbb::flow::make_edge(q1, tbb::flow::input_port1(j)); tbb::flow::make_edge(j, q3); int n 10; for (int i 1; i n; i) { q0.try_put(msg_t{i, 1000i}); q1.try_put(msg_t{n-i, 10000i}); } g.wait_for_all(); for (int i 1; i n; i) { std::tuplemsg_t, msg_t t; q3.try_get(t); std::cout std::get0(t) : std::get1(t) std::endl; } return 0; }这段程序构建了一个典型的键匹配流图q0依次放入(1,1001)到(8,1008)q1依次放入(8,10008)到(1,10001)join_node按键k将两个端口的消息配对如k1时配成(1,1001)与(1,10001)最终在q3中按序输出配对结果。相比传统写法USE_FUNCTION或USE_ADL路径下构造join_node的代码量显著减少且新增输入端口时无需重复修改取键逻辑。源码级实现原理取键函数体的模板封装扩展在内部通过模板体key_from_message_body把对key_from_message的调用包装为函数对象实现位于 _flow_graph_join_impl.h#if __TBB_PREVIEW_MESSAGE_BASED_KEY_MATCHING template typename K, typename T struct key_from_message_body { K operator()(const T t) const { return key_from_messageK(t); } }; // Adds const to reference type template typename K, typename T struct key_from_message_bodyK,T { const K operator()(const T t) const { return key_from_messageconst K(t); } }; #endif /* __TBB_PREVIEW_MESSAGE_BASED_KEY_MATCHING */这里有两个值得注意的细节显式模板实参调用key_from_messageK(t)以显式模板实参形式调用因此用户自定义的key_from_message必须至少把键类型K作为模板参数即必须是一个模板函数才能匹配这里的调用形式引用键类型的特化当键类型是引用如key_matchingint时第二个特化会为K形态补充const返回const K避免产生悬垂引用测试 test_join_node_msg_key_matching.cpp 中即包含key_matchingint、key_matchingstd::string的并行用例。构造函数的接线type_to_key_function_body_leafjoin_node(graph g)构造函数本身定义在unfolded_join_node针对key_matching策略的特化中_flow_graph_join_impl.h#if __TBB_PREVIEW_MESSAGE_BASED_KEY_MATCHING unfolded_join_node(graph g) : base_type(g, func_initializer_type( new type_to_key_function_body_leafTypes, K, key_from_message_bodyK, Types (key_from_message_bodyK, Types())...)) {} #endif其机制是借助 C 参数包展开对OutputTuple中的每一个输入类型Types分别实例化一个type_to_key_function_body_leafTypes, K, key_from_message_bodyK, Types把默认取键体与各端口绑定等价于“自动为每个端口补齐取键函数”。这正是该扩展能消除每端口手工传函数这一重复劳动的根本原因——展开后每个端口的行为与传统方式逐个传入Bodies...的构造同文件unfolded_join_node(graph g, Bodies... bodies)完全一致。type_to_key_function_body_leaf及其上级包装type_to_key_function_body在同一文件中承担键函数体的类型擦除与调用职责见 _flow_graph_join_impl.h 中针对 key_matching 端口类型key_matching_port的关联用法即键匹配端口在收到消息时通过该函数体把消息转换为键再在节点内部按键进行配对与缓冲。测试验证与适用范围仓库中针对该预览特性提供了专门的测试套件test_join_node_msg_key_matching.cpp核心测试文件覆盖串行与并行场景测试组合包括“带key()成员函数的类型 不带key()仅靠 ADL 的类型”键类型覆盖int、std::string以及引用形态int、std::string同时在支持 C17 推导指引deduction guides的编译环境下验证join_node j3(j0)的类型推导正确性test_join_node_msg_key_matching_n_args.cpp多参数多端口版本测试验证端口数量大于 2 时的取键行为test_join_node_preview.cpp预览特性总开关下的相关用例。测试中的MyMessageKeyWithBrokenKey、MyMessageKeyWithoutKey、MyMessageKeyWithoutKeyMethod等类型定义于 test_join_node.h分别用于验证key()成员函数可用、key()缺失需回退 ADL、仅 ADL 可用等不同组合说明只要消息类型满足“有key()成员函数”或“命名空间内有key_from_message模板”二者之一即可使用join_node(graph g)构造。注意事项与现状预览特性接口可能变化该实验性特性最早出现于 2015 年目前仍以预览形式存在。RFC 文档README.md明确指出特性若要转正需要先现代化其实现方案评估key()成员函数 ADL 是否仍是最优简化方式、考虑替代方案并被加入 oneTBB 正式规范。因此生产代码中使用时应关注版本演进必须显式开启宏忘记定义TBB_PREVIEW_FLOW_GRAPH_FEATURES 1将无法使用新构造函数编译期会退化为需要传函数对象的传统形态自定义函数必须是模板由于内部以key_from_messageK(t)显式指定模板实参调用用户的key_from_message重载必须带K作为模板参数如template typename K K key_from_message(const MessageK)命名空间必须一致ADL 查找要求用户重载与消息类型处于同一命名空间链接形式该扩展属于 oneTBB 头文件库header-only 的 flow_graph 部分能力无需额外链接动态库但需确保 oneTBB 运行时库可用。延伸阅读特性参考文档docs/main/reference/type_specified_message_keys.rst本文依据的主体文档特性规范草稿docs/main/specification/source/uncategorized/flow_graph/type_specified_message_keys.rst实验特性与设计讨论rfcs/experimental/flow_graph_msg_keys/README.md默认key_from_message实现include/oneapi/tbb/flow_graph.hkey_from_message_body与构造函数实现include/oneapi/tbb/detail/_flow_graph_join_impl.h测试用例test/tbb/test_join_node_msg_key_matching.cpp、test/tbb/test_join_node_msg_key_matching_n_args.cpp【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表