
mold 仓库内嵌 oneAPI TBB 指南enumerable_thread_specific 的 combine 与 combine_each 合并机制【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold本文围绕 mold 仓库内嵌的 oneAPI TBBoneAPI Threading Building Blocks规范文档 combining.rst 展开系统讲解线程本地容器enumerable_thread_specific提供的两个串行合并接口二元归约combine()与逐元素访问combine_each()。读完本文你将掌握这两个接口的签名约束、空容器语义、底层实现细节以及它们与combinable的委托关系并能在并行归约场景中正确选用。enumerable_thread_specific下称 ETS是 oneAPI TBB 提供的线程本地存储容器每个访问它的线程拥有一份独立的T类型副本副本惰性创建容器整体又可通过迭代器与范围被当作一个普通容器使用。当并行计算结束后需要把散落在各线程本地副本中的部分结果收拢为最终结果时就轮到本规范文档所定义的合并接口登场。本仓库中该容器的完整声明见 enumerable_thread_specific_cls.rst其源码实现位于 enumerable_thread_specific.h。一、合并接口的定位与总体约定规范文档开宗明义地指出The member functions in this section iterate across the entire container sequentially in the calling thread.即本节Combining的所有成员函数都在调用线程内串行地遍历整个容器。这意味着合并阶段是纯串行的归约过程不会引入额外的并行度因此二元/一元函数对象被假定为非良性non-benign即允许携带副作用。ETS 类模板签名默认参数为template typename T, typename Allocator cache_aligned_allocatorT, ets_key_usage_type ETS_key_type ets_no_key class enumerable_thread_specific;容器头部注释对combine/combine_each给出了三条重要约定见 enumerable_thread_specific.h两个方法均为 ETS 定义其中combine()要求类型T定义有operator()两者都不会修改容器内容但不保证传入的函数对象本身不去修改元素两者都在串行上下文中求值默认视为非良性操作。二、combine()跨线程副本的二元归约签名与要求templatetypename BinaryFunc T combine(BinaryFunc f);按规范文档BinaryFunc必须满足 ISO C 标准 [function.objects] 一节描述的Function Objects要求且具体应为结合律associative二元函数对象签名形如T BinaryFunc(T,T)或T BinaryFunc(const T,const T)。参数T必须与enumerable_thread_specific的模板参数一致。结合律这一要求至关重要combine只是顺序地把元素两两合并并不承诺任何特定合并次序因此只有满足f(f(a,b),c) f(a,f(b,c))的函数如加法、乘法、std::plus才被允许。这与oneapi::tbb::parallel_reduce对归约函数的要求一脉相承。语义归约、空容器与初值规范给出三条语义Effects使用二元函数对象f对所有元素计算归约reduction空容器若没有任何元素则按照创建线程本地元素的同一套规则生成结果即用默认构造、Finit初始化函数或 exemplar 副本构造出结果Returns返回归约结果。源码实现解析enumerable_thread_specific.h 中的实现与规范完全对应// CombineFunc has signature T(T,T) or T(const T, const T) template typename CombineFunc T combine(CombineFunc f_combine) { if(begin() end()) { ets_elementT location; my_construct_callback-construct(location.value()); return *location.value_committed(); } const_iterator ci begin(); T my_result *ci; while(ci ! end()) my_result f_combine( my_result, *ci ); return my_result; }从实现可见两个关键细节空容器路径当begin() end()时构造一个临时的ets_elementT经由my_construct_callback-construct(...)按创建线程本地副本的同一回调规则构造元素并立即返回其值。这也解释了为何combine()要求T可拷贝/可赋值operator——归约过程my_result f_combine(...)依赖赋值操作。非空路径以首元素*ci作为归约初值my_result随后按内部存储顺序底层my_locals是concurrent_vector见 enumerable_thread_specific.h逐一执行f_combine(my_result, *ci)。由于二元函数满足结合律该顺序不影响最终结果。实战示例#include oneapi/tbb/enumerable_thread_specific.h #include oneapi/tbb/parallel_for.h #include oneapi/tbb/blocked_range.h #include numeric // 每个线程累加自己的部分和 oneapi::tbb::enumerable_thread_specificlong long sums{0LL}; oneapi::tbb::parallel_for( oneapi::tbb::blocked_rangeint(0, N), { long long s sums.local(); for (int i r.begin(); i r.end(); i) s i; }); // 串行归约出最终和空容器时返回 0由初始化函数 finit 决定 long long total sums.combine(std::pluslong long{});测试套件中同样大量使用该模式例如 conformance_enumerable_thread_specific.cpp 分别以值语义与引用语义的加法函数对象调用combine并断言归约结果等于期望和conformance_combinable.cpp 则验证了combinable在 copy/assign/move 之后combine结果依然正确以及clear()后归约结果归零。三、combine_each()对每个本地副本逐一求值签名与要求templatetypename UnaryFunc void combine_each(UnaryFunc f);UnaryFunc同样须满足 [function.objects] 的 Function Objects 要求具体为一元函数对象接受下列任一签名void UnaryFunc(T) // 按值接收 void UnaryFunc(T) // 可变引用接收 void UnaryFunc(const T) // 常量引用接收T必须与 ETS 的模板参数一致。语义副作用驱动的遍历规范定义Effects为对*this中的每个实例x求值f(x)。与combine()不同combine_each()不产生归约结果返回void它的价值在于允许对每个线程本地副本执行任意副作用操作——累加到外部累加器、清空副本、统计数量等。源码实现解析enumerable_thread_specific.h 的实现极简// combine_func_t takes T by value or by [const] reference, and returns nothing template typename CombineFunc void combine_each(CombineFunc f_combine) { for(iterator ci begin(); ci ! end(); ci) { f_combine( *ci ); } }逐元素地把本地副本的引用*ci传给f_combine。由于传入的是引用采用T签名的函数对象可以就地修改各线程本地副本combine()则无法做到这一点它只读地合并到局部变量。测试 conformance_enumerable_thread_specific.cpp 专门验证了combine_each通过T修改本地值的能力并断言 combine_each does not allow to modify thread local values 的反例不成立。实战示例累加并清空long long total 0; // 第一步把每个线程的部分和累加进 total sums.combine_each( { total v; }); // 第二步就地清空所有本地副本 sums.combine_each( { v 0; }); // 第三步验证已被清空 sums.combine_each( { assert(v 0); });这正是 conformance_enumerable_thread_specific.cpp 中Accumulator/ClearingAccumulator/AssertClean三连调用的真实写照先累加再累加并清空最后断言副本全部干净。四、combine与combine_each的选型对比维度combine(f)combine_each(f)函数对象二元需满足结合律一元无结合律要求返回值T归约结果void空容器行为按创建规则构造一个临时结果并返回循环体不执行直接返回是否要求operator是否修改本地副本不能只读合并能T签名典型用途求和、求积、取极值等归约汇总到外部变量、清空、统计选型原则很直接需要一个合并后的值用combine需要对每个副本做副作用操作或就地修改用combine_each。两者均串行遍历、均不修改容器结构本身。五、与combinable的委托关系oneAPI TBB 还提供了更轻量的combinable容器其完整规范见 combinable_cls.rst。从源码看combinable本质上是 ETS 的薄封装combinable.htemplate typename T class combinable { using my_ets_type typename tbb::enumerable_thread_specificT, my_alloc, ets_no_key; my_ets_type my_ets; public: // ... template typename CombineFunc T combine(CombineFunc f_combine) { return my_ets.combine(f_combine); } template typename CombineFunc void combine_each(CombineFunc f_combine) { my_ets.combine_each(f_combine); } };因此本文对 ETScombine/combine_each的一切语义分析要求、Effects、空容器规则、串行遍历都直接适用于combinable。二者的差异在于combinable固定使用ets_no_key的缓存策略与cache_aligned_allocator模板参数只有T而 ETS 还支持自定义分配器与ets_key_per_instance/ets_suspend_aware等底层实现选择。如果你只是需要一个并行计算后合并的临时容器combinable更简洁若还需要跨容器迭代、并行range()、分段迭代器等能力则应直接使用 ETS。六、注意事项与边界情况串行执行语义combine/combine_each均在调用线程内顺序执行规范明示函数对象若被并行场景之外的代码复用需自行保证其正确性ETS 头文件注释也强调二者被视为非良性non-benign操作enumerable_thread_specific.h。不修改容器两个方法本身不改变 ETS 内容combine_each以T传参时对副本的修改由用户函数对象负责容器结构元素集合不变。combine的拷贝/赋值要求combine内部用my_result f_combine(...)反复赋值要求T可默认/按回调构造且可赋值若T不可赋值应改用combine_each聚合到外部变量。ETS 头文件明确the contained objects need not have operator() defined if combine is not usedenumerable_thread_specific.h。线程标识复用ETS 使用操作系统返回的std::this_thread::get_id()区分线程该值只在线程存续期内唯一新建线程可能拿到已销毁线程的 ID导致本地副本数可能少于实际访问线程数见 enumerable_thread_specific_cls.rst 的 caution。因此不要假设combine的元素个数等于应用线程数测试 conformance_enumerable_thread_specific.cpp 展示了主线程与若干工作线程各自写入后combine的期望值计算需把主线程副本一并计入。结合律是正确性前提combine的遍历顺序取决于内部concurrent_vector的创建顺序实现细节规范并不承诺固定次序归约函数必须满足结合律才能保证结果与顺序无关。七、在 mold 仓库中的阅读指引enumerable_thread_specific的合并接口是理解 TBB 线程本地归约管线的关键一环。在 mold 仓库中可按以下路径继续深挖规范文档combining.rst、enumerable_thread_specific_cls.rst、combinable_cls.rst容器实现enumerable_thread_specific.hcombine/combine_each见第 1028–1049 行、combinable.h一致性测试conformance_enumerable_thread_specific.cpp、conformance_combinable.cpp其中覆盖了空容器、copy/move 后归约、clear()后归零、combine_each就地修改等边界场景。一句话总结并行阶段用local()累积串行收尾用combine()归约、用combine_each()做副作用处理这就是 ETS 合并机制的完整用法闭环。【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考