官方tutorial https://software.intel.com/en-us/tbb-user-guide
1.1串行版本:
void SerialApplyFoo( float a[], size_t n ) {
for( size_t i=0; i!=n; ++i )
Foo(a[i]);
}
并行版本:
首先定义一个类:需要实现operator () 重载和构造函数
operator()中执行具体操作(和串行版本执行的操作一样),注意必须是const类型
构造函数中用于将外部需要操作的变量转换为类中的成员变量。
block_range& t 是当前线程中迭代的空间,是需处理的全部数据的一部分。
#include "tbb/tbb.h"
using namespace tbb;
class ApplyFoo {
float *const my_a;
public:
void operator()( const blocked_range& r ) const {
float *a = my_a;
for( size_t i=r.begin(); i!=r.end(); ++i )
Foo(a[i]);
}
ApplyFoo( float a[] ) :
my_a(a)
{}
};
使用方法:
#include "tbb/tbb.h"
void ParallelApplyFoo( float a[], size_t n ) {
parallel_for(blocked_range(0,n), ApplyFoo(a));
}
1.2 lambda函数版本:
#include "tbb/tbb.h"
using namespace tbb;
void ParallelApplyFoo( float* a, size_t n ) {
parallel_for( blocked_range(0,n),
[=](const blocked_range& r) {
for(size_t i=r.begin(); i!=r.end(); ++i)
Foo(a[i]);
}
);
}
和串行版相比需要修改的内容很少
parallel_for的第二个参数是一个lambda函数,[=]表示对定义在函数外部的变量的传递方法,[=]表示值传递,[&]表示引用传递。
1.3 设置并行粒度
#include "tbb/tbb.h"
void ParallelApplyFoo( float a[], size_t n ) {
parallel_for(blocked_range(0,n,G), ApplyFoo(a),
simple_partitioner());
}
block_range的第三个参数可以设置粒度G,parallel_for的第三个参数设置分配任务方法
分割粒度G小,则并行程度相对高,但会做更多并行前的预处理任务。每个并行子任务至少要执行100,000个时钟周期才能保证较高效率。当不能确定时,可以先设为100000,再用二分的方法向下调节找到最优值。
分配方法和粒度的关系:(默认为simple_partition)
simple_partitioner Chunksize bounded by grain size. g/2 ≤ chunksize ≤ g
auto_partitioner (default)[4]
Automatic chunk size. g/2 ≤ chunksize
affinity_partitioner Automatic chunk size, cache affinity and uniform distribution of iterations.
static_partitioner Deterministic chunk size, cache affinity and uniform distribution of iterations without load balancing. max(g/3, problem_size/num_of_resources) ≤ chunksize
当各个线程的工作需要汇总时需要使用parallel_reduce
2.1 串行版本
float SerialSumFoo( float a[], size_t n ) {
float sum = 0;
for( size_t i=0; i!=n; ++i )
sum += Foo(a[i]);
return sum;
}
2.2 并行版本同样需要定义一个类,并需要实现以下方法:
1) operator() 重载:具体执行的操作,和parallel_for不同的是不需要是const型了
2) split拷贝构造函数:
SumFoo( SumFoo& x, split ) : my_a(x.my_a), my_sum(0) {}
该函数在当前线程将任务分配给子线程时执行
split是tbb定义好的元素,用于区别构造函数
3) join 方法:
void join( const SumFoo& y ) {my_sum+=y.my_sum;}
该函数用于合并两个线程计算的结果
4) 构造函数:用于将外部数据转换为类的成员变量。
完整代码如下:
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) {} };
调用方法是:
float ParallelSumFoo( const float a[], size_t n ) {
SumFoo sf(a);
parallel_reduce( blocked_range(0,n), sf );
return sf.my_sum;
}
在实现operator时有个问题,因为同一个对象可能会负责多个子空间的处理,所以在每次执行operator时不能将之前已有累积值的成员变量赋为0,如下面的代码
class SumFoo {
...
public:
float my_sum;
void operator()( const blocked_range& r ) {
...
float sum = 0; // WRONG – should be 'sum = my_sum". ... for( ... )