TBB使用教程

官方tutorial https://software.intel.com/en-us/tbb-user-guide

1.parallel_for:

常规方式(非lambda函数):

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

2. parallel_reduce

当各个线程的工作需要汇总时需要使用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( ... ) 

你可能感兴趣的:(C++)