原文

循环可以进行归约,如以下求和:

float SerialSumFoo( float a[], size_t n ) {float sum = 0;for( size_t i=0; i!=n; ++i )sum += Foo(a[i]);return sum;
}

如果迭代是独立的,你可以使用模板类 parallel_reduce 并行化此循环,如下所示:

float ParallelSumFoo( const float a[], size_t n ) {SumFoo sf(a);parallel_reduce( blocked_range<size_t>(0,n), sf );return sf.my_sum;
}

SumFoo 类指定了归约的细节,例如如何累加和组合它们。 这是类 SumFoo 的定义:

class SumFoo {float* my_a;
public:float my_sum;void operator()( const blocked_range<size_t>& r ) {float *a = my_a;float sum = my_sum;size_t end = r.end();for( size_t i=r.begin(); i!=end; ++i )sum += Foo(a[i]);my_sum = sum;}SumFoo( SumFoo& x, split ) : my_a(x.my_a), my_sum(0) {}void join( const SumFoo& y ) {my_sum+=y.my_sum;}SumFoo(float a[] ) :my_a(a), my_sum(0){}
};

请注意与来自 parallel_for 的类 ApplyFoo 的区别。 首先,operator() 不是 const。 这是因为它必须更新 SumFoo::my_sum。 其次,SumFoo 有一个拆分构造函数和一个方法 join,必须存在才能使 parallel_reduce 工作。 拆分构造函数将原始对象的引用和类型为 split 的伪参数作为参数,该参数由库定义。 虚拟参数将拆分构造函数与复制构造函数区分开来。

在示例中,operator() 的定义将局部临时变量(asumend)用于循环内访问的标量值。 这种技术可以通过让编译器清楚地知道这些值可以保存在寄存器而不是内存中来提高性能。 如果这些值太大而无法放入寄存器中,或者它们的地址以编译器无法跟踪的方式获取,则该技术可能无济于事。 对于典型的优化编译器,仅对写入的变量(例如示例中的 sum)使用局部临时变量就足够了,因为这样编译器就可以推断出循环不会写入任何其他位置,并将其他读取提升到外部 循环。

当工作线程可用时,由任务调度程序决定,parallel_reduce 调用拆分构造函数为工作线程创建子任务。 当子任务完成时,parallel_reduce 使用方法 join 来累积子任务的结果。 下图顶部的图形显示了当工作程序可用时发生的拆分连接序列:


上图中的箭头表示时间顺序。 拆分构造函数可能会在对象 x 用于缩减的前半部分时并发运行。 因此,创建 y 的拆分构造函数的所有操作都必须相对于 x 成为线程安全的。 因此,如果拆分构造函数需要递增与其他对象共享的引用计数,则应使用原子递增。
如果 worker 不可用,则使用减少前半部分的相同主体对象减少迭代的后半部分。 那就是下半场的减持开始,上半场的减持结束。

由于如果 worker 不可用,则不使用 split/join,parallel_reduce 不一定会进行递归拆分。
由于同一个主体可能用于累积多个子范围,因此 operator() 不丢弃较早的累积是至关重要的。 下面的代码显示了 SumFoo::operator() 的错误定义。

class SumFoo {...
public:float my_sum;void operator()( const blocked_range<size_t>& r ) {...float sum = 0;  // WRONG – should be 'sum = my_sum"....for( ... )sum += Foo(a[i]);my_sum = sum;}...
};

由于错误,主体返回最后一个子范围的部分和,而不是 parallel_reduce 应用它的所有子范围。
parallel_reduce 的分区器和粒度规则与 parallel_for 相同。
parallel_reduce 推广到任何关联操作。 通常,拆分构造函数会做两件事:

  • 复制运行循环体所需的只读信息。
  • 将归约变量初始化为操作的标识元素。
    join 方法应该进行相应的合并。 你可以同时进行多次缩减:可以使用单个 parallel_reduce 收集最小值和最大值。

归约运算可以是非交换的。 如果浮点加法被字符串连接替换,该示例仍然有效。

Advanced Example

更高级的关联操作的一个例子是找到 Foo(i) 最小化的索引。 串行版本可能如下所示:

long SerialMinIndexFoo( const float a[], size_t n ) {float value_of_min = FLT_MAX;        // FLT_MAX from <climits>long index_of_min = -1;for( size_t i=0; i<n; ++i ) {float value = Foo(a[i]);if( value<value_of_min ) {value_of_min = value;index_of_min = i;}}return index_of_min;
}

该循环记录迄今为止找到的最小值以及该值的索引。 这是循环迭代之间携带的唯一信息。 要将循环转换为使用 parallel_reduce,函数对象必须跟踪携带的信息,以及当迭代分布在多个线程上时如何合并这些信息。 此外,函数对象必须记录指向 a 的指针以提供上下文。
以下代码显示了完整的函数对象。

class MinIndexFoo {const float *const my_a;
public:float value_of_min;long index_of_min;void operator()( const blocked_range<size_t>& r ) {const float *a = my_a;for( size_t i=r.begin(); i!=r.end(); ++i ) {float value = Foo(a[i]);if( value<value_of_min ) {value_of_min = value;index_of_min = i;}}}MinIndexFoo( MinIndexFoo& x, split ) :my_a(x.my_a),value_of_min(FLT_MAX),    // FLT_MAX from <climits>index_of_min(-1){}void join( const SumFoo& y ) {if( y.value_of_min<value_of_min ) {value_of_min = y.value_of_min;index_of_min = y.index_of_min;}}MinIndexFoo( const float a[] ) :my_a(a),value_of_min(FLT_MAX),    // FLT_MAX from <climits>index_of_min(-1),{}
};

现在 SerialMinIndex 可以使用 parallel_reduce 重写,如下所示:

long ParallelMinIndexFoo( float a[], size_t n ) {MinIndexFoo mif(a);parallel_reduce(blocked_range<size_t>(0,n), mif );return mif.index_of_min;
}

Advanced Topic: Other Kinds of Iteration Spaces

到目前为止的示例都使用了类 blocks_range<T> 来指定范围。 这个类在许多情况下都很有用,但它并不适合所有情况。 你可以使用 oneTBB 来定义自己的迭代空间对象。 该对象必须通过提供一个基本的拆分构造函数、一个可选的比例拆分构造函数(伴随着启用其使用的特征值)和两个谓词方法来指定如何将其拆分为子空间。 如果你的类称为 R,则方法和构造函数应如下所示:

class R {// True if range is emptybool empty() const;// True if range can be split into non-empty subrangesbool is_divisible() const;// Splits r into subranges r and *thisR( R& r, split );// Splits r into subranges r and *this in proportion pR( R& r, proportional_split p );// Allows usage of proportional splitting constructorstatic const bool is_splittable_in_proportion = true;...
};

如果范围为空,方法 empty 应该返回 true。如果范围可以拆分为两个非空子空间,则方法 is_divisible 应该返回 true,这样的拆分值得开销。基本的拆分构造函数应该有两个参数:

  • 第一个 R 类型
  • 第二个 oneapi::tbb::split 类型

第二个参数没有被实际使用;它仅用于将构造函数与普通的复制构造函数区分开来。基本的拆分构造函数应该尝试将 r 大致拆分为两半,并将 r 更新为前半部分,并将构造的对象设置为后半部分。
与基本拆分构造函数不同,比例拆分构造函数是可选的,并采用 oneapi::tbb::proportional_split 类型的第二个参数。该类型具有返回比例值的 leftright 方法。应该使用这些值来相应地拆分 r,使更新后的 r 对应于比例的左侧部分,而构造的对象对应于右侧部分。只有在类中定义了静态常量 is_splittable_in_proportion 并赋值为 true 时,才会使用比例拆分构造函数。
两个拆分构造函数都应该保证更新后的 r 部分和构造的对象不为空。只有当 r.is_divisible 为真时,并行算法模板才会调用 r 上的拆分构造函数。
迭代空间不必是线性的。查看 oneapi/tbb/blocked_range2d.h 以获得二维范围的示例。它的拆分构造函数尝试沿其最长轴拆分范围。当与parallel_for 一起使用时,它会导致循环以提高缓存使用率的方式“递归阻塞”。这种良好的缓存行为意味着,在blocked_range2d<T> 上使用parallel_for 可以使循环比顺序等效的循环运行得更快,即使在单个处理器上也是如此。

Intel TBB 开发指南 3 parallel_reduce相关推荐

  1. Intel TBB 开发指南 1 发布包与编译

    原文 发布包内容 TBB 包括适用于 Windows*.Linux* 和 macOS* 操作系统的动态共享库文件.头文件和代码示例,你可以按照本节所述进行编译和运行. Debug tbb_debug ...

  2. Intel TBB 开发指南 2 Parallelizing Simple Loops

    原文 Initializing and Terminating the Library TBB 2.2 及更高版本会自动初始化任务调度程序. 你可以使用类 task_scheduler_init 显式 ...

  3. Intel TBB开发指南 5 并行化数据流和依赖图

    原文 除了循环并行,oneTBB 库还支持并行图(Graph).可以创建高度可扩展的图,但也可以创建完全连续的图. 使用并行图,计算由**节点(Node)表示,这些计算之间的通信通道由边 (Edge) ...

  4. Linux 汇编语言开发指南

    Linux 汇编语言开发指南 肖文鹏 (xiaowp@263.net), 北京理工大学计算机系硕士研究生 本文作者 肖文鹏是北京理工大学计算机系的一名硕士研究生,主要从事操作系统和分布式计算环境的研究 ...

  5. Intel TBB简介及在Windows7 VS2013上源码的编译过程

    Intel TBB(Intel Threading Building Blocks)是Intel线程构建块开源库,它的License是Apache 2.0. Intel TBB是一种用于并行编程的基于 ...

  6. 详细介绍Intel SGX开发环境搭建和Hello Enclave程序运行

    这里记录Windows10下搭建Intel SGX开发环境和编写第一个Hello World程序的过程. 总共分为两部分,第一是环境搭建,第二是hello world程序编写.每一步都有截图,希望以通 ...

  7. 《iOS 8开发指南(第2版)》——第1章,第1.3节工欲善其事,必先利其器——搭建开发环境...

    本节书摘来自异步社区<iOS 8开发指南(第2版)>一书中的第1章,第1.1节1.3 工欲善其事,必先利其器--搭建开发环境,作者 管蕾,更多章节内容可以访问云栖社区"异步社区& ...

  8. 【转】PF_RING开发指南

    转自:PF_RING开发指南-yww680169-ChinaUnix博客 1.       概述 PF_RING是Luca Deri发明的提高内核处理数据包效率,并兼顾应用程序的补丁,如Libpcap ...

  9. hello,intel TBB

    Intel Threading BuildingBlocks(Intel TBB)是一个使用ISO C++代码实现的多平台.可扩展并行编程库.但目前为止这方面的中文资料却很少.初步了解TBB时,并非每 ...

最新文章

  1. java调用clang编译的so_写Java这么久,JDK源码编译过没?编译JDK源码踩坑纪实
  2. 高德地图关键字搜索oc版
  3. shell编程【分发系统】
  4. mysql 5.02审计_CentOS 7.2 mysql-5.7.17 审计插件安装、开启与设定
  5. Windows 家族的十二种常用密码破解法
  6. HTTP_REFERER的获取情况
  7. 洛谷 P1736 创意吃鱼法
  8. build 之前执行task_浅谈VS编译自定义编译任务—MSBuild Task(csproject)-阿里云开发者社区...
  9. 【STM32】手把手固件库开发工程建立
  10. c语言 vscode代码自动补全_借助C/C++ Extension实现VSCode C++代码补全
  11. 实战篇|风控策略效率的测试、调优与评估
  12. CPU的温度是360的准还是鲁大师的准?
  13. Deployment vs ReplicationController in Kubernetes
  14. 55)函数指针和其意义
  15. VS编译NPAPI:error C2065: “PCONTEXT”: 未声明的标识符
  16. c语言语法口诀,一般过去时语法口诀
  17. 费下载最新版万能视频格式转换器是一款功能强大的全能视频格式转换软件
  18. c语言考场排座系统,具才考场座次编排系统
  19. ORDER BY语句报错Out of sort memory, consider increasing server sort buffer size
  20. git 配置winmerge

热门文章

  1. ImportError:cannot import name ‘namedtuple‘from ‘collections‘(C:\Python\Pyth...
  2. elasticsearch服务自动断掉
  3. 产业安全专家谈丨数字经济高速发展,数据要素安全该如何保障?
  4. usermod -a -G group1 user1
  5. python全局代理_Python3 中代理使用方法总结
  6. 甲骨文官网下载jdk历史版本
  7. 微信小程序使用wxparse插件,渲染文章不换行问题
  8. 电脑触摸屏无法使用、失灵解决办法
  9. 2016-8-17晨型养成第二天
  10. A component required a bean of type 'com.xxx.xxx.service.xxxService' that could not be found的debug报错