ProTBB (四):设计模式与 ProTBB

C++并行应用程序设计与优化


并行应用程序中最常见的三种并行层(layers)

  • The message-driven layer
  • The fork-join layer
  • Single Instruction, Multiple Data (SIMD) layer

The message-driven layer

  • 将规模较大的计算结构化,通过消息相互通信
  • 常见模式包括
    • Streaming Graphs
      • Streaming Graph Processing
      • 一种用于处理实时数据流的计算模型
      • 结合了图计算和流式数据处理的概念
      • 将图数据作为一个数据流,遍历图数据中的边执行计算的框架
    • Data Flow Graphs
      • 用 nodes 和 edges 的有向图来描述计算过程
    • Dependency Graphs
      • 表示实体之间的依赖关系
  • TBB 的 Flow Graph 接口支持这种类型的并行

The fork-join layer

  • 面对一个大规模任务,先串行将任务拆分为多个可并行执行的子任务,再进行并行计算 (fork)
  • 当所有并行计算的子任务完成后再继续执行串行部分的逻辑(join)
  • 常见模式包括
    • functional parallelism (task parallelism)
    • parallel loops
    • parallel reductions
    • pipelines
  • TBB 通过 Generic Parallel Algorithms 支持

Single Instruction, Multiple Data (SIMD) layer

  • 通过同时对多个数据元素应用相同的操作来实现的
  • 这种类型的并行性通常使用矢量扩展(例如 AVX、AVX2 和 AVX-512)来实现
    • 这些扩展使用每个处理器核心中可用的矢量单元
    • VS 中有专门设置使用某类矢量扩展的编译选项

并行设计模式

设计并行算法的建议

  • 建议程序员需要通过四个设计空间来开发并行程序
    • 寻找并发性 Finding concurrency
      • TBB 自动做了负载均衡,我们只需要保证获得一个连续的任务
    • 算法结构 Algorithm structures
      • 使用并行设计模式设计并行算法
    • 支撑结构 Supporting structures
      • 从算法设计转入到算法实现
      • 考虑如何组织并行程序以及用于管理共享(尤其是可变)数据的技术
    • 实施机制 Implementation mechanisms
      • 包括线程管理和同步
      • TBB 处理所有线程管理,我们只需要关心更高设计级别的任务
  • 有效的模式
    • 实现并行可扩展性的两个先决条件是良好的数据局部性和避免开销
    • 将算法策略抽象为语义并且实现已被证明在实践中非常有效的模式
      • 这种分离关注的方式使得可以分别推理高层算法设计和底层(通常是特定于机器的)细节
  • Data Parallelism Wins 数据并行是最好的并行算法设计策略
    • 可扩展并行性的最佳总体策略是数据并行性 (data parallelism)
      • 并行性随着问题规模的增长而增强
    • 通常,数据被分成块,每个块都通过单独的任务进行处理
    • 有时是平均分割的, 有时是递归分割的
    • 重要的是更大的数据集会产生更多的任务
    • 一般来说,无论问题是规则的还是不规则的,数据并行都可以应用
    • 数据并行的对立面是功能分解 (functional decomposition)(也称为任务并行 task parallelism)
      • 是一种并行运行不同程序功能的方法
      • 功能分解充其量可以将性能提高一个常数因子
      • 有时,功能分解可以提供满足性能目标所需的额外并行性
        • 但这不应该是主要策略,因为它无法扩展

设计模式

嵌套模式 (Nesting Pattern)

  • 因嵌套支持而得到的结果有两个含义
    • 在选择是否应该调用 TBB 模板时,不需要知道我们是处于“并行区域”还是“串行区域”
      • 由于使用 TBB 只是创建任务,所以不必担心线程的超额订阅
    • 不需要担心调用 TBB 编写的库,以及控制它是否可以使用并行性
  • 嵌套可以被认为是一种元模式 (meta-pattern)
    • 因为它意味着模式可以分层组合
    • 有模块化和可组合性的嵌套模式是在设计 TBB 时思考的关键

Map Pattern

  • 此模式所表达的独立性使其具有很好的可扩展性
  • Map Pattern 是并行编程的最佳模式
    • 将工作划分为统一的独立部分,这些部分并行运行,没有依赖性
    • 这代表了一种常规并行化 (regular parallelization )
      • 称为尴尬并行化 (embarrassing parallelism)
      • Map Pattern 值得在任何可能的情况下使用
        • 因为它允许高效的并行化和高效的矢量化
    • Map Pattern 不涉及各部分之间共享的可变状态
    • 映射函数(独立的工作部分)必须是 “纯粹的”,因为它不能修改共享状态
      • std::share_ptr 可能具有共享含义
  • parallel_for 适用于 Map Pattern
  • parallel_invoke 可用于少量映射类型的并行化
    • 但有限的数量不会提供太多可扩展性

工作堆模式 (Workpile Pattern)

  • 一种广义的 Map Pattern
    • 每个实例(映射函数)可以生成更多实例
    • 工作可以添加到要做的 “一堆” 事情中
      • 如,可以用于树的递归搜索,希望生成实例来处理树的每个节点的每个子节点
    • 工作堆模式比映射模式更难矢量化
  • parallel_for_each 适用于工作堆模式

Reduction Patterns (Reduce and Scan) 归约模式(归约和扫描)

  • 可以被认为是一个映射操作
    • 其中每个子任务都会产生一个子结果
    • 需要将其组合起来形成最终的单一答案
  • 使用关联的 “组合器函数” (combiner function) 来组合多个子结果
  • 由于舍入变化,浮点数计算会有精度误差
  • parallel_reduce 适用于 Reduction Pattern
    • parallel_definistic_reduce 更加精确
  • 典型的组合器
    • 加法、乘法、最大值、最小值、以及布尔运算 AND、OR 和 XOR
  • 扫描模式
    • 并行计算前缀
    • 具有固有串行依赖性的场景中非常有用
      • 先拆分数据,并行-reduce,再基于 reduce 结果进行并行-reduce
    • parallel_scan 适用于 Scan Pattern
  • 寻找最大值 parallel_reduce 实现
     int maxValue = tbb::parallel_reduce(
       tbb::blocked_range<int>(0, a.size()),
       std::numeric_limits<int>::min(),
       [&](const tbb::blocked_range<int>& r, int init) -> int
       {
         
         
           for(int i = r.begin(); i != r.end(); ++i)
           {
         
         
               init = std::max(init, a[i]);
           }
           return init;
       },
       []
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值