ARTICLE DETAIL

资讯详情

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

mold 仓库内嵌 TBB 深度解析:parallel_pipeline 的 filter 类模板与过滤链组合机制

mold 仓库内嵌 TBB 深度解析:parallel_pipeline 的 filter 类模板与过滤链组合机制 mold 仓库内嵌 TBB 深度解析parallel_pipeline 的 filter 类模板与过滤链组合机制【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold导读本文围绕 mold 仓库third-party/tbb子模块中 oneAPI TBBThreading Building Blocks规范文档所定义的filter类模板展开它是parallel_pipeline流水线算法的强类型核心构件一个filter以模板参数声明输入/输出类型通过仿函数functor承载具体计算逻辑并可用operator()将多个过滤器首尾相接组成一条类型安全的过滤链。阅读本文后你将掌握filter的构造、组合、filter_mode三种执行模式、C17 推导指引、flow_control输入流终止协议以及它们在流水线算法中“生产—变换—消费”的实际用法并能在 mold 仓库内找到对应的头文件实现、可运行示例与一致性测试作为实证。1. 什么是 filter强类型过滤器的定位在 oneAPI TBB 的parallel_pipeline算法中一条流水线由一系列过滤器filter串联而成数据以“令牌token”形式从第一个过滤器流向最后一个过滤器。filter类模板即为这一模型中的强类型过滤器它的模板参数直接声明了过滤器输入和输出的数据类型从而把“哪个过滤器的输出喂给哪个过滤器的输入”这一约束提升到编译期检查的层面。规范文档 filter_cls.rst 明确指出filter可由一个仿函数构造也可通过operator()组合两个filter对象同一个filter对象可以被复用在多个表达式中例如将同一变换过滤器接入多条流水线filter类只能与parallel_pipeline函数配合使用不应脱离流水线单独执行。其完整类声明如下来自规范文档与实际头文件一致// Defined in header oneapi/tbb/parallel_pipeline.h namespace oneapi { namespace tbb { templatetypename InputType, typename OutputType class filter { public: filter() default; filter( const filter rhs ) default; filter( filter rhs ) default; void operator(const filter rhs) default; void operator( filter rhs ) default; templatetypename Body filter( filter_mode mode, const Body body ); filter operator( const filterOutputType,OutputType right ); void clear(); } templatetypename T, typename U, typename Body filterT,U make_filter( filter::mode mode, const Body f ); templatetypename T, typename V, typename U filterT,U operator( const filterT,V left, const filterV,U right ); } // namespace tbb } // namespace oneapi2. filter_mode三种执行模式的含义filter_mode是描述过滤器执行方式的枚举。规范文档 filter_mode_enum.rst 定义了三个枚举值枚举值语义parallel可并行处理多个数据项且不保证处理顺序serial_out_of_order一次只处理一个数据项不保证处理顺序serial_in_order一次只处理一个数据项处理顺序由流水线中第一个serial_in_order过滤器隐式决定其余所有serial_in_order过滤器均遵循该顺序serial_in_order之所以重要是因为它提供了数据项之间的有序性屏障典型场景是最后阶段需要按输入顺序累加或写出结果如聚合求和、按序写文件而中间的parallel阶段则充分释放多核并行度。从源码看该枚举在 parallel_pipeline.h 中以unsigned int为底层类型实现其位组合直接映射到内部节点标记enum class filter_mode : unsigned int { parallel base_filter::filter_is_out_of_order, serial_in_order base_filter::filter_is_serial, serial_out_of_order base_filter::filter_is_serial | base_filter::filter_is_out_of_order };可见parallel与serial_out_of_order都允许乱序执行区别在于后者被额外标记为串行filter_is_serial因此同一时刻只有一个线程在处理serial_in_order则在此基础上进一步承担顺序基准的角色。3. filter 的成员函数与生命周期3.1 默认构造与“未定义过滤器”filter()默认构造函数产生一个未定义过滤器undefined filter。规范文档特别给出 caution 提示对未定义过滤器使用operator()或将其传入parallel_pipeline其行为是未定义的undefined。因此默认构造只应作为占位或延迟初始化手段。这一约束在源码层面有直接体现parallel_pipeline.h 中operator()用__TBB_ASSERT对左右操作数做了运行期检查templatetypename T, typename V, typename U filterT,U operator( const filterT,V left, const filterV,U right ) { __TBB_ASSERT(left.my_root,cannot use default-constructed filter as left argument of ); __TBB_ASSERT(right.my_root,cannot use default-constructed filter as right argument of ); return filter_node_ptr( new (r1::allocate_memory(sizeof(filter_node))) filter_node(left.my_root,right.my_root) ); }3.2 仿函数构造templatetypename Body filter( filter_mode mode, const Body body )使用body的一份拷贝构造过滤器它将把InputType类型的输入值映射为OutputType类型的输出值并按照mode指定的模式执行。源码中该构造函数在 parallel_pipeline.h 内部把仿函数包装进filter_node_leafInputType, OutputType, Body叶子节点并将模式转换为unsigned int标记。3.3 组合赋值与清空operator( const filterOutputType,OutputType right )就地完成*this *this right把right追加到当前过滤链之后。注意其参数类型被约束为filterOutputType, OutputType即右操作数的输入类型必须等于当前过滤器左操作数的输出类型从类型层面保证链路不断裂。clear()把*this置回未定义过滤器。源码实现等价于用filter()赋值即my_root nullptr;。4. 非成员函数make_filter 与 operator4.1 make_filter类型安全的过滤器工厂templatetypename T, typename U, typename Func filterT, U make_filter( filter::mode mode, const Func f )make_filter返回filterT, U(mode, f)即显式指定输入输出类型并构造过滤器。它是构造过滤链最常用的入口。头文件 parallel_pipeline.h 实际上提供了两个重载make_filterInputType, OutputType(mode, body)完全显式指定类型make_filter(mode, body)无显式模板实参通过filter_inputBody与filter_outputBody从仿函数签名自动推导输入/输出类型见下文推导指引。4.2 operator过滤器的组合templatetypename T, typename V, typename U filterT,U operator( const filterT,V left, const filterV,U right )operator返回一个代表left与right组合的新过滤器组合的行为等价于 left 的输出值成为 right 的输入值。模板参数结构filterT,V filterV,U - filterT,U是关键的类型契约——左操作数的输出类型V必须与右操作数的输入类型V一致否则编译期即报错。源码实现中组合产生一个新的filter_node其子节点为left.my_root与right.my_root见上节代码因此组合操作是轻量的指针级拼接不拷贝仿函数本体。整个过滤链最终通过反复归约为一个filtervoid,void才能交给parallel_pipeline。4.3 推导指引C17 自动推断类型templatetypename Body filter(filter_mode, Body) - filterfilter_inputBody, filter_outputBody;该 C17 推导指引允许直接写filter(mode, body)而省略模板实参。其中filter_inputBody是Body::operator()输入参数类型的别名若输入参数类型是flow_control则filter_inputBody为void这正是首过滤器输入为void的语义来源filter_outputBody是Body::operator()返回类型的别名。头文件中该指引位于 parallel_pipeline.h并受__TBB_CPP17_DEDUCTION_GUIDES_PRESENT宏保护——仅在支持 C17 推导指引的编译环境下生效。5. 仿函数体Body的四类需求filter的模板参数决定了其 Body 需要满足哪一类命名需求。规范文档 filter_body.rst 按过滤器在流水线中的位置区分了四种形态过滤器形态模板参数Body 需求伪签名职责首过滤器FirstFilterBodyInputType voidOutputType operator()( oneapi::tbb::flow_control fc ) const从输入流取出下一个数据项输入流结束调用fc.stop()尾过滤器LastFilterBodyOutputType voidvoid operator()( InputType item ) const处理消费收到的数据项中间过滤器MiddleFilterBody两者均非voidOutputType operator()( InputType item ) const处理收到的数据项并返回变换结果单过滤器SingleFilterBody两者均为voidvoid operator()( oneapi::tbb::flow_control fc ) const从输入流取元素处理流结束调用fc.stop()规范文档还注明了两条 C17 起的放宽规则若OutputType为voidBody也可以是InputType的成员函数指针若InputType、OutputType均非voidBody还可以是InputType的返回OutputType的成员函数指针或InputType中类型为OutputType的数据成员指针——这使Class::member这类表达式可以直接充当过滤器体极大简化了“抽取字段/调用方法”式流水线的写法。5.1 flow_control输入流终止协议flow_control是首过滤器用于通知“输入流到达末尾”的机制。规范文档 flow_control_cls.rst 说明parallel_pipeline会把一个flow_control对象传给首过滤器的仿函数仿函数到达输入流末尾时应调用fc.stop()并返回一个不会被传给下一级过滤器的哑值dummy value。其接口极简namespace oneapi { namespace tbb { class flow_control { public: void stop(); }; }}在源码示例中可看到该协议的实际落点mold 仓库 TBB 示例 square.cpp 的首过滤器在读到 0 字节且缓冲为空时执行fc.stop(); return nullptr;从而结束整个流水线。6. parallel_pipeline过滤链的装配与执行filter本身不直接执行必须装配成filtervoid,void过滤链后交给parallel_pipeline。规范文档 parallel_pipeline_func.rst 给出了函数签名void parallel_pipeline( size_t max_number_of_live_tokens, const filtervoid,void filter_chain ); void parallel_pipeline( size_t max_number_of_live_tokens, const filtervoid,void filter_chain, task_group_context context );max_number_of_live_tokens流水线中并发存活的令牌数阈值。它限定了任意时刻同时在各级过滤器间流动的数据项数量上限是控制内存占用与并行度的关键参数filter_chain由若干过滤器经拼接而成的整条链可选参数contexttask_group_context让流水线任务在指定上下文含取消/异常语义中执行缺省时算法运行在自身绑定的上下文中。组装一条流水线的通用写法parallel_pipeline( max_number_of_live_tokens, make_filtervoid,I1(mode0,g0) make_filterI1,I2(mode1,g1) make_filterI2,I3(mode2,g2) ... make_filterIn,void(moden,gn) );类型约束贯穿始终首过滤器的输入类型必须是void尾过滤器的输出类型必须是voidoperator要求左操作数的第二模板实参与右操作数的第一模板实参一致即filterT,V filterV,U。规范文档进一步说明实际并行处理的数据项数量取决于流水线结构与可用线程数max_number_of_live_tokens仅设定并发处理项数的上界。6.1 官方示例均方根RMS三阶段流水线规范文档给出的示例用三段过滤器计算序列first, last)的均方根float RootMeanSquare( float* first, float* last ) { float sum0; parallel_pipeline( /*max_number_of_live_token*/16, make_filtervoid,float*( filter_mode::serial_in_order, [- float*{ if( firstlast ) { return first; } else { fc.stop(); return nullptr; } } ) make_filterfloat*,float( filter_mode::parallel, [](float* p){return (*p)*(*p);} ) make_filterfloat,void( filter_mode::serial_in_order, {sumx;} ) ); return sqrt(sum); }该示例完整呈现了三种过滤器的协作模式首过滤器serial_in_order输出float*作为生产者每次调用返回下一个元素的指针耗尽时调用fc.stop()并返回nullptr哑值中间过滤器parallel输入float*、输出float平方计算对顺序无要求可多线程并行执行是流水线性能的关键释放点尾过滤器serial_in_order输入float、输出void累加求和必须保持输入顺序故取serial_in_order以保证与生产顺序一致。max_number_of_live_token 16意味着最多 16 个数据项同时在流水线中“飞行”从而在吞吐与内存之间取得平衡。6.2 仓库可运行示例square 文本平方流水线mold 仓库内的 square.cpp 是另一个完整可运行实例它从文件中按块读取十进制整数文本逐块求平方后写出。其首过滤器MyInputFunc::operator()(oneapi::tbb::flow_control fc)正是 FirstFilterBody 形态TextSlice* operator()(flow_control fc) const通过fread填充预分配的TextSlice缓冲当读入 0 字节且当前分片为空时调用fc.stop()并返回nullptr结束输入流。该示例同时展示了TextSlice::allocate/free配合tbb_allocator进行大块缓冲管理的手法可作为编写“文件 → 变换 → 文件”类 IO 流水线的直接参考。7. 源码与测试实证7.1 头文件实现脉络在 parallel_pipeline.h 中filter的存储与组合实现非常简洁filter内部仅持有filter_node_ptr my_root一个指针拷贝/移动构造与赋值均为浅拷贝共享节点仿函数构造与make_filter都通过r1::allocate_memory分配filter_node_leafInputType, OutputType, Body把mode转为unsigned int存入节点operator分配filter_node组合节点将左右子节点挂接成树整条链递归组合后根节点类型为filtervoid,void最终由r1::parallel_pipeline(task_group_context, size_t, const d1::filter_node)执行声明见 parallel_pipeline.h。这种“叶子节点存仿函数、组合节点存子树、链头类型收敛到filtervoid,void”的结构从实现层面印证了文档所述的“强类型 指针级组合”设计。7.2 一致性测试覆盖TBB 自带的一致性测试 conformance_parallel_pipeline.cpp 对本文所述机制做了系统验证值得作为研读配套类型正确性静态断言make_filterI,O(mode, ...)与无显式模板实参的make_filter(mode, ...)均须产生filterI,O见 测试第 214-219 行三段式链装配首void→middle、中middle→output、尾output→void三个serial_in_order过滤器经拼接后运行见 测试第 421-423 行多种filter_mode与 token 数组合下的行为验证测试首行附近以n_tokens与mode参数化驱动。此外test/CMakeLists.txt 中通过tbb_add_test(... NAME test_parallel_pipeline ...)将该测试接入 TBB 的测试构建体系说明filter/parallel_pipeline具备完整的回归测试保障。8. 使用要点与易错项小结基于规范文档与源码将filter的使用要点归纳如下类型链必须闭合整条链必须从filtervoid, X开始、以filterY, void结束最终组合为filtervoid, void才能传给parallel_pipeline中间每一对操作都要求输出/输入类型匹配。切勿使用默认构造的过滤器未定义过滤器参与或传入parallel_pipeline行为未定义源码中的__TBB_ASSERT仅用于调试期捕获此类误用。首过滤器必须遵守flow_control协议输入流结束时要调用fc.stop()并返回哑值否则流水线无法正常终止。顺序约束靠serial_in_order表达需要按生产顺序消费数据的阶段聚合、按序写文件必须使用serial_in_order且全链顺序由第一个serial_in_order过滤器确立。复用过滤器对象同一个filter可安全复用于多个表达式节点以指针共享可借此把公共变换阶段抽成可复用的中间过滤器。利用 C17 推导与成员指针filter(mode, body)与make_filter(mode, body)可省去手写模板实参Class::method、Class::data这类成员指针在满足相应模板参数条件下可直接充当 Body。9. 延伸阅读filter类的规格定义filter_cls.rst执行模式枚举规格filter_mode_enum.rst输入流终止机制flow_control_cls.rst流水线算法总览与官方 RMS 示例parallel_pipeline_func.rstBody 四类命名需求filter_body.rst头文件实现parallel_pipeline.h可运行示例square.cpp一致性测试conformance_parallel_pipeline.cpp任务组上下文流水线的可选执行上下文task_group_context_cls.rst【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表