一、OpenMP TBB选择
- OpenMP需要编译器支持,TBB需要下载运行库
- TBB 大量使用了 C++ 模板和用户自定义类型,所以特别适宜对象导向程度较高的代码, 但TBB只支持C++
二、TBB parallel_for
2.1 构建类或者结构体
依据实际问题,构建类或者结构体,主要包含初始化和重载函数运算符,注意这里重载函数运算符需要加入const限定,避免更改函数体。
1 struct MT{
2 MT(...){}
3 void operator() (const tbb::blocked_range<int>& r) const{}
4 };
2.2 调用
默认参数调用
调用tbb::parallel_for
,先初始化一个类或结构体,设置并行的一个范围,可以采用如下调用方式。
1 MT mt(datas, ratio);
2 tbb::parallel_for(tbb::blocked_range<int>(0, static_cast<int>(datas.size())), mt);
控制块大小
在传入范围给tbb::parallel_for
时可以控制其分块的大小,blocked_range<T>(begin,end,grainsize)
中grainsize
默认大小为1,如果想将并行时每块大小设置为2,可以更改grainsize
的大小,如下所示:
1 MT mt(datas, ratio);
2 tbb::parallel_for(tbb::blocked_range<int>(0, static_cast<int>(datas.size()), 2), mt);
如上图所示case A 和case B 具有相同的总灰色区域。case A 中粒度太小有较高的并行度,但会导致资源开销比例相对较高,case B 中相对大一点的粒度可以降低此比例,但也降低了并行度。实际使用时可以参考硬件资源和并行度对grainSize
进行跳转。
官网示例给出了不同grainsize
对应的时间消耗。
带宽及缓存
由数据集和缓存的相对大小决定的亲和力的好处如下:
parallel_for
提供了不同的方式
官网示例演示了affinity_partitioner
和auto_partitioner
两种方式的运行时间对比。
1 MT mt(datas, ratio);
2 static tbb::affinity_partitioner ap;
3 tbb::parallel_for(tbb::blocked_range<int>(0, static_cast<int>(datas.size()), 2), mt, ap);
2.3 加锁
并行时有时会不同线程访问同一对象,避免冲突可以在相应位置加锁,然后执行完再解锁。
1 mutex_.lock(); //加锁
2 mutex_.unlock(); //解锁
2.4 完整示例
1 struct MT {
2 MT(std::vector<double>& datas, double ratio, tbb::mutex& mutex) : datas_(&datas), ratio_(ratio), mutex_(mutex)
3 {}
4
5 void operator() (const tbb::blocked_range<int>& r) const
6 {
7 // mutex_.lock();
8 for (int idx = r.begin(); idx != r.end(); ++idx)
9 {
10 (*datas_)[idx] *= ratio_;
11 }
12 // mutex_.unlock();
13 }
14
15 static tbb::mutex mutex_;
16 std::vector<double>* datas_;
17 const double ratio_;
18 };
19
20 // tbb::mutex CompresionMT::mutex_; // global define not need init
21
22 void TestTBB(std::vector<double>& datas, double ratio, bool bMT)
23 {
24 if (bMT) {
25 /*std::vector<int> edgeOrders;
26 for (int eId = 0; eId < datas.size(); ++eId) {
27 edgeOrders.push_back(eId);
28 }
29 MT mt(edgeOrders, datas, ratio); */
30 tbb::mutex mutex; // if global define the not need
31 MT mt(datas, ratio, mutex);
32 static tbb::affinity_partitioner ap;
33 tbb::parallel_for(tbb::blocked_range<int>(0, static_cast<int>(datas.size()),2), mt, ap);
34 }
35 else {
36 for (int idx = 0; idx < datas.size(); ++idx) {
37 datas[idx] *= ratio;
38 }
39 }
40 }
1 #include <chrono>
2 std::vector<double> datas1, datas2;
3 int num = 10000000;
4 for (int i = 1; i <= num; ++i) {
5 datas1.push_back(i);
6 datas2.push_back(i);
7 }
8 double ratio = 2.0;
9 std::chrono::time_point<std::chrono::steady_clock> t1 = std::chrono::steady_clock::now();
10 TestTBB(datas1, ratio, false);
11 std::chrono::time_point<std::chrono::steady_clock> t2 = std::chrono::steady_clock::now();
12 std::chrono::duration<double> elapsed1 = t2 - t1;
13 std::cout << "t2 - t1: " << elapsed1.count() << "s\n"<< std::endl;
14
15 std::chrono::time_point<std::chrono::steady_clock> t3 = std::chrono::steady_clock::now();
16 TestTBB(datas2, ratio, true);
17 std::chrono::time_point<std::chrono::steady_clock> t4 = std::chrono::steady_clock::now();
18 std::chrono::duration<double> elapsed2 = t4 - t3;
19 std::cout << "t4 - t3: " << elapsed2.count() << "s\n" << std::endl;