ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

oneAPI TBB flattened2d 详解:将“容器之容器“扁平化遍历的适配器类模板

2026/9/14 14:09:12 拓冰建站 浏览量
oneAPI TBB flattened2d 详解:将“容器之容器“扁平化遍历的适配器类模板 oneAPI TBB flattened2d 详解将容器之容器扁平化遍历的适配器类模板【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold导读flattened2d是 oneAPI Threading Building BlocksoneTBB即本仓库third-party/tbb目录所包含的 TBB 发行版在oneapi/tbb/enumerable_thread_specific.h头文件中提供的类模板本质是一个零拷贝的容器之容器container of containers扁平化视图适配器。它的典型应用场景是配合enumerable_thread_specific当每个线程各自维护一个容器、由外层容器按线程汇总时flattened2d让你像遍历一个普通一维容器那样顺序访问所有线程容器的全部元素而无需手工拼接或复制数据。读完本文你将掌握flattened2d/flatten2d的完整接口、构造语义、迭代器行为与并发安全约定并可通过源码与 conformance 测试理解其底层实现机制。1. 设计定位TLS 场景下的分段容器统一视图1.1 为什么需要扁平化视图oneTBB 的线程局部存储Thread Local Storage, TLS章节中enumerable_thread_specific为每个线程按需惰性创建一个元素副本因此一个常见用法是每个线程的本地副本本身就是一个容器如std::vectorint线程在并行循环中向自己的副本追加数据最后把所有线程副本拼起来统一处理。正如 thread_local_storage.rst 中所述This section also describes class templateflatten2d, which assists a common idiom where anenumerable_thread_specificrepresents a container partitioner across threads.即flattened2d专门服务于以enumerable_thread_specific作为跨线程的容器分区器这一惯用法。外层容器segmented container的每个元素是一个内层容器flattened2d把这个二维结构摊平为一维元素序列。1.2 与普通拷贝拼接的区别flattened2d不拥有任何数据它只保存外容器指针与两个外迭代器my_begin、my_end遍历时按需跳转内层容器。这意味着零元素拷贝不产生新的一维容器也就不产生内存搬移惰性视图构造成本为 O(1)仅保存迭代器size()需要 O(外层元素数) 遍历求和只读语义限制视图依赖外容器的生命周期外容器必须在视图存续期间保持有效。2. 完整接口与声明规范原文继承依据 flattened2d_cls.rst规范标识[tls.flattened2d]flattened2d的定义如下// Defined in header oneapi/tbb/enumerable_thread_specific.h namespace oneapi { namespace tbb { templatetypename Container class flattened2d { public: // Basic types using size_type /* implementation-defined */; using difference_type /* implementation-defined */; using allocator_type /* implementation-defined */; using value_type /* implementation-defined */; using reference /* implementation-defined */; using const_reference /* implementation-defined */; using pointer /* implementation-defined */; using const_pointer /* implementation-defined */; using iterator /* implementation-defined */; using const_iterator /* implementation-defined */; explicit flattened2d( const Container c ); flattened2d( const Container c, typename Container::const_iterator first, typename Container::const_iterator last ); iterator begin(); iterator end(); const_iterator begin() const; const_iterator end() const; size_type size() const; }; template typename Container flattened2dContainer flatten2d(const Container c); template typename Container flattened2dContainer flatten2d( const Container c, const typename Container::const_iterator first, const typename Container::const_iterator last); } // namespace tbb } // namespace oneapi前置要求Container类型必须满足 ISO C 标准 [container.requirements.general] 章节的容器要求。从源码看实际需要外层容器支持begin()/end()迭代、其value_type内层容器支持size()、empty()、begin()/end()以及随机或双向访问能力详见第 4 节实现分析。3. 构造函数与非成员工厂函数3.1explicit flattened2d( const Container c )构造一个flattened2d表示外层容器c中所有内层容器元素构成的序列。等价于用c.begin()和c.end()调用两参数版本。std::vectorstd::vectorint vv { {1, 2}, {3}, {}, {4, 5, 6} }; oneapi::tbb::flattened2ddecltype(vv) f(vv); // 或 auto f flatten2d(vv); // 视图中元素序列1, 2, 3, 4, 5, 6空内层容器自动跳过3.2flattened2d( const Container c, first, last )构造一个flattened2d表示容器c中半开区间[first, last)内的内层容器元素构成的序列。当只需要扁平化外层容器的一部分例如只汇总某几个线程的分区时使用此版本。3.3 非成员工厂函数flatten2dtemplate typename Container flattened2dContainer flatten2d(const Container c); template typename Container flattened2dContainer flatten2d(const Container c, const typename Container::const_iterator b, const typename Container::const_iterator e);两个重载分别对应上述两个构造函数返回新构造的flattened2d对象。利用类模板参数推导CTAD实践中通常写作auto f flatten2d(vv);即可免去显式书写模板参数。3.4 并发安全约定Safety规范明确标注了两类不同的安全级别操作并发安全说明两个构造函数不得对同一个flattened2d对象并发调用构造/修改类操作size()允许对同一个flattened2d对象并发调用只读操作换言之视图对象创建完成后多个线程可以安全地同时查询size()或各自持有一个迭代器并行遍历迭代器遍历本身是只读的但同一对象上并发构造是不允许的。此外由于视图保存的是外容器指针外容器在整个视图使用期间不得被修改或销毁——这是所有视图类如std::string_view的共性约束。4. 迭代器语义forward-only 的分段遍历4.1 规范要求规范原文明确指出Iterating frombegin()toend()visits all of the elements in the inner containers. The class template supports forward iterators only.即begin()指向扁平化序列的第一个元素end()指向序列末尾越过最后一个元素迭代器类别为前向迭代器forward iterator只支持、、!、*、-不支持随机访问无n、[]、--等遍历会覆盖视图所辖全部内层容器的全部元素。4.2 源码实现segmented_iteratorflattened2d的iterator/const_iterator类型别名指向内部类模板segmented_iterator见 enumerable_thread_specific.husing iterator segmented_iteratorContainer, value_type; using const_iterator segmented_iteratorContainer, const value_type;segmented_iterator内部维护三份状态见 L602-L605SegmentedContainer* my_segcont; // 外层容器指针 outer_iterator outer_iter; // 外层容器迭代器 inner_iterator inner_iter; // 当前内层容器迭代器其迭代推进逻辑advance_me见 L607-L615体现了分段跳转的核心思想void advance_me() { inner_iter; while(inner_iter outer_iter-end() outer_iter ! my_segcont-end()) { inner_iter outer_iter-begin(); } }即先在当前内层容器内前进当内层容器耗尽时跳到下一个外层元素内层容器并重新从它的begin()开始。配合赋值运算符中的跳空逻辑见 L555-L565——operator(const outer_iterator)在定位时跳过所有empty()的内层容器——最终遍历恰好覆盖所有非空内层容器的全部元素。值得注意的若干实现细节iterator_category声明为std::input_iterator_tag见 L532因此它同时满足std::accumulate、std::for_each等单遍算法的要求解引用operator*内含__TBB_ASSERT断言防止对end()或空内层容器解引用见 L592-L597segmented_iterator支持从outer_iterator直接赋值以及const_iterator与iterator之间的模板化转换见 L534-L550因此const_iterator it f.begin()与iterator可以相互比较。5. 成员函数逐一解析5.1size()—— 元素总数返回flattened2d中所有可见内层容器大小之和。源码实现见 L1083-L1089非常简单size_type size() const { size_type tot_size 0; for(typename Container::const_iterator i my_begin; i ! my_end; i) { tot_size i-size(); } return tot_size; }即对[my_begin, my_end)范围内的每个内层容器累加其size()。复杂度 O(k)k 为外层容器中可见的内层容器个数规范允许其在同一对象上并发调用。5.2begin()/end()及其 const 重载四个重载分别返回普通/常量迭代器指向扁平化序列的首元素与尾后位置。flattened2d的构造与迭代器生成源码见 L1072-L1081。6. 典型使用模式与完整示例6.1 与enumerable_thread_specific组合并行分区汇总经典惯用法每个线程向自己的std::vector副本中追加数据主线程最后用flatten2d统一遍历。#include oneapi/tbb/enumerable_thread_specific.h #include oneapi/tbb/parallel_for.h #include vector #include numeric #include cstdio int main() { // 每个线程一个 vector 副本 oneapi::tbb::enumerable_thread_specificstd::vectorint ets; oneapi::tbb::parallel_for(0, 1000, { ets.local().push_back(i); // 各线程只写自己的副本无需加锁 }); // 把“容器之容器”扁平化为只读序列 auto flat oneapi::tbb::flatten2d(ets); std::printf(total elements: %zu\n, flat.size()); // 应为 1000 long long sum 0; for (auto it flat.begin(); it ! flat.end(); it) { sum *it; } std::printf(sum: %lld\n, sum); // 应为 01...999 return 0; }由于enumerable_thread_specific满足容器要求外层可迭代、元素为内层容器flatten2d(ets)直接可用并且由于每个线程的副本由 TBB 惰性创建空副本同样会被segmented_iterator自动跳过size()与遍历结果不受线程实际调度数量影响。6.2 子区间扁平化当只想处理外层容器的部分分区时使用两参数版本auto c ets; // 外层容器 auto first c.begin(); auto last first; std::advance(last, 2); // 只取前两个线程副本 auto partial oneapi::tbb::flatten2d(c, first, last);6.3 与 STL 单遍算法协同segmented_iterator声明为std::input_iterator_tag可直接配合单遍算法#include numeric auto flat oneapi::tbb::flatten2d(vv); int total std::accumulate(flat.begin(), flat.end(), 0); int mx *std::max_element(flat.begin(), flat.end());注意前向迭代器不支持std::sort之类的多遍/随机访问算法。7. 仓库内的验证证据7.1 Conformance 测试TBB 的合规性测试文件 conformance_enumerable_thread_specific.cpp 在文件头明确标注其覆盖目标//! \file conformance_enumerable_thread_specific.cpp//! \brief Test for [tls.enumerable_thread_specific tls.flattened2d] specification测试核心逻辑见 L670-L713验证了auto fvs flatten2d(vs);构造后fvs.size()等于期望元素总数N*pconst_iterator与iterator的构造、赋值与相等比较it3 cit; REQUIRE(it3 cit);operator-与解引用的一致性it3.operator-() (*it3)分别用const_iterator与iterator完整遍历元素计数均与size()一致两参数版本flatten2d(vvs)在并行归约后再次验证元素总数N*p pvrb2.count*25。这组断言从侧面印证了第 4、5 节所述扁平化遍历覆盖全部内层元素、空容器被跳过、两种迭代器可互相转换。7.2 生产代码中的真实使用仓库内 polyover.cppparallel_for多边形的 polygon overlay 示例也引用了flattened2d可作为并行分区 统一遍历惯用法的端到端参考实现。8. 使用注意事项小结生命周期flattened2d内部保存外层容器的指针实现上通过const_cast去除 const 以便保存非 const 指针见 L1072-L1076外容器必须存活于视图使用期间请勿在视图存续时向外层容器添加/删除元素否则迭代器可能失效。并发安全构造后同一flattened2d的size()可并发调用但构造操作不可在同一对象上并发共享视图时各线程应持有自己的迭代器。迭代器能力仅前向按实现为输入迭代器类别只支持单遍顺序访问不要对扁平化序列使用随机访问或逆向遍历。只读遍历const_iterator提供只读访问虽然实现上segmented_iterator的inner_iterator本身可写但规范定义的适配器主要面向遍历与聚合修改内层容器需谨慎并保证外层结构不被破坏。size()开销O(可见内层容器个数) 的实时求和若频繁调用可先缓存结果。9. 延伸阅读TLS 章节总览thread_local_storage.rst其中还介绍了combinable与enumerable_thread_specific两个类模板类模板完整源码enumerable_thread_specific.hflattened2d位于 L1053-L1105segmented_iterator位于 L500-L616规范合规测试conformance_enumerable_thread_specific.cpp实战示例polyover.cpp综上flattened2d是一个轻量、零拷贝的二维容器扁平化视图接口极简两个构造函数 begin/end/size 两个flatten2d工厂函数实现精巧segmented_iterator分段跳转、自动跳过空容器与enumerable_thread_specific的组合正是 TBB 官方文档推荐的并行分区汇总惯用法。掌握它之后你可以在并行程序中安全、高效地统一处理每线程一个容器的数据布局。【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考