c++中两个知名的多核并行运算编程库,一个是intel开发的TBB,一个是微软开发的PPL

并行库充分利用多核的优势,通过并行运算提高程序效率,本文主要介绍c++中两个知名的并行库,一个是intel开发的TBB,一个是微软开发的PPL。本文只介绍其基本的常用用法:并行算法和任务。
 
1. 在多核的平台上开发并行化的程序,必须合理地利用系统的资源 - 如与内核数目相匹配的线程,内存的合理访问次序,最大化重用缓存。有时候用户使用(系统)低级的应用接口创建、管理线程,很难保证是否程序处于最佳状态。

2. Intel Thread Building Blocks (TBB) 很好地解决了上述问题:
a)TBB提供C++模版库,用户不必关注线程,而专注任务本身。
b)抽象层仅需很少的接口代码,性能上毫不逊色。
c)灵活地适合不同的多核平台。
d)线程库的接口适合于跨平台的移植(Linux, Windows, Mac)
e)支持的C++编译器 – Microsoft, GNU and Intel  

3.主要的功能:
1)通用的并行算法
循环的并行:
parallel_for, parallel_reduce – 相对独立的循环层
parallel_scan – 依赖于上一层的结果
流的并行算法
parallel_while – 用于非结构化的流或堆
pipeline - 对流水线的每一阶段并行,有效使用缓存
并行排序
parallel_sort – 并行快速排序,调用了parallel_for

2)任务调度者
管理线程池,及隐藏本地线程复杂度
并行算法的实现由任务调度者的接口完成
任务调度者的设计考虑到本地线程的并行所引起的性能问题

3)并行容器
concurrent_hash_map
concurrent_vector
concurrent_queue

4)同步原语
atomic
mutex
spin_mutex – 适合于较小的敏感区域
queuing_mutex – 线程按次序等待(获得)一个锁
spin_rw_mutex
queuing_rw_mutex
说明:使用read-writer mutex允许对多线程开放”读”操作


5)高性能的内存申请 
使用TBB的allocator 代替 C语言的 malloc/realloc/free 调用
使用TBB的allocator 代替 C++语言的 new/delete 操作


使用TBB的例子 – task
#include “tbb/task_scheduler_init.h”
#include “tbb/task.h”
using namespace tbb;
class ThisIsATask: public task {
public:
  task* execute () {
     WORK ();
     return NULL;
  }
};

class MyRootTask: public task {
public:
  task* execute () {
    for (int i=0; i<N; i++) {
       task& my_task = *new (task::allocate_additional_child_of (*this)) ThisIsATask ();
       spawn (my_task);
    } 
    wait_for_all ();
    return NULL;
  }
};

int main () {
  task_scheduler_init my_tbb;  // 创建线程池
  task& my_root =
    *new (task::allocate_root()) MyRootTask ();
  my_root.set_ref_count (1);
  task::spawn_root_and_wait (my_root); // 开始Root Task任务
  return 0;
}
 
TBB(Intel® Threading Building Blocks )
TBB是intel用标准c++写的一个开源的并行计算库。它的目的是提升数据并行计算的能力,可以在他的官网上 下载最新的库和文档。TBB主要功能:
 
并行算法
任务调度
并行容器
同步原语
内存分配器
TBB并行算法
 
parallel_for:并行方式遍历一个区间。
 
parallel_for(1, 20000, [](int i){cout << i << endl; });
parallel_for(blocked_range<size_t>(0, 20000), [](blocked_range<size_t>& r)
{
    for (size_t i = r.begin(); i != r.end(); ++i)
        cout << i << endl; 
});
parallel_do和parallel_for_each:将算法应用于一个区间
 
vector<size_t> v;
parallel_do(v.begin(), v.end(), [](size_t i){cout << i << endl; });
parallel_for_each(v.begin(), v.end(), [](size_t i){cout << i << endl; });
 parallel_reduce
 
  类似于map_reduce,但是有区别。它先将区间自动分组,对每个分组进行聚合(accumulate)计算,每组得到一个结果,最后将各组的结果进行汇聚(reduce)。这个算法稍微复杂一点,parallel_reduce(range,identity,func,reduction),第一个参数是区间范围,第二个参数是计算的初始值,第三个参数是聚合函数,第四个参数是汇聚参数。
 
复制代码
float ParallelSum(float array [], size_t n) {
    return parallel_reduce(
        blocked_range<float*>(array, array + n),
        0.f,
        [](const blocked_range<float*>& r, float value)->float {
            return std::accumulate(r.begin(), r.end(), value);
    },
        std::plus<float>()
        );
}
复制代码
这个对数组求和的例子就是先自动分组然后对各组中的元素进行聚合累加,最后将各组结果汇聚相加。
 
parallel_pipeline:并行的管道过滤器
 
  数据流经过一个管道,在数据流动的过程中依次要经过一些过滤器的处理,其中有些过滤器可能会并行处理数据,这时就可以用到并行的管道过滤器。举一个例子,比如我要读入一个文件,先将文件中的数字提取出来,再将提取出来的数字做一个转换,最后将转换后的数字输出到另外一个文件中。其中读文件和输出文件不能并兴去做,但是中间数字转换的环节可以并行去做的。parallel_pipeline的原型:
 
parallel_pipeline( max_number_of_live_tokens, 
                   make_filter<void,I1>(mode0,g0) &
                   make_filter<I1,I2>(mode1,g1) &
                   make_filter<I2,I3>(mode2,g2) &
                   ... 
                   make_filter<In,void>(moden,gn) );
  第一个参数是最大的并行数,我们可以通过&连接多个filter,这些filter是顺序执行的,前一个filter的输出是下一个filter的输入。
 
复制代码
float RootMeanSquare( float* first, float* last ) {
    float sum=0;
    parallel_pipeline( /*max_number_of_live_token=*/16,       
        make_filter<void,float*>(
            filter::serial,
            [&](flow_control& fc)-> float*{
                if( first<last ) {
                    return first++;
                 } else {
                    fc.stop();
                    return NULL;
                }
            }    
        ) &
        make_filter<float*,float>(
            filter::parallel,
            [](float* p){return (*p)*(*p);} 
        ) &
        make_filter<float,void>(
            filter::serial,
            [&](float x) {sum+=x;}
        )
    );
    return sqrt(sum);
}
复制代码
  第一个filter生成数据(如从文件中读取数据等),第二个filter对产生的数据进行转换,第三个filter是对转换后的数据做累加。其中第二个filter是可以并行处理的,通过filter::parallel来指定其处理模式。
 
parallel_sort:并行排序
 
const int N = 1000000;
float a[N];
float b[N];
parallel_sort(a, a + N);
parallel_sort(b, b + N, std::greater<float>());
parallel_invoke:并行调用,并行调用多个函数
 
void f();
extern void bar(int);
 
void RunFunctionsInParallel() {
    tbb::parallel_invoke(f, []{bar(2);}, []{bar(3);} );
}
TBB任务
task_group表示可以等待或者取消的任务集合
 
task_group g;
g.run([]{TestPrint(); });
g.run([]{TestPrint(); });
g.run([]{TestPrint(); });
g.wait();
 
 
PPL(Parallel Patterns Library)
  PPL是微软开发的并行计算库,它的功能和TBB是差不多的,但是PPL只能在 windows上使用。二者在并行算法的使用上基本上是一样的, 但还是有差异的。二者的差异:
 
parallel_reduce的原型有些不同,PPL的paraller_reduce函数多一个参数,原型为parallel_reduce(begin,end,identity,func,reduction), 比tbb多了一个参数,但是表达的意思差不多,一个是区间,一个是区间迭代器。
PPL中没有parallel_pipeline接口。
TBB的task没有PPL的task强大,PPL的task可以链式连续执行还可以组合任务,TBB的task则不行。
PPL任务的链式连续执行then
 
复制代码
int main()
{
    auto t = create_task([]() -> int
    { 
        return 0;
    });
 
    // Create a lambda that increments its input value.
    auto increment = [](int n) { return n + 1; };
 
    // Run a chain of continuations and print the result. 
    int result = t.then(increment).then(increment).then(increment).get();
    cout << result << endl;
}
/* Output:
    3
*/
复制代码
PPL任务的组合
 
  1.when_all可以执行一组任务,所有任务完成之后将所有任务的结果返回到一个集合中。要求该组任务中的所有任务的返回值类型都相同。
 
复制代码
array<task<int>, 3> tasks =
{
    create_task([]() -> int { return 88; }),
    create_task([]() -> int { return 42; }),
    create_task([]() -> int { return 99; })
};
 
auto joinTask = when_all(begin(tasks), end(tasks)).then([](vector<int> results)
{
    cout << "The sum is " 
          << accumulate(begin(results), end(results), 0)
          << '.' << endl;
});
 
// Print a message from the joining thread.
cout << "Hello from the joining thread." << endl;
 
// Wait for the tasks to finish.
joinTask.wait();
复制代码
2.when_any任务组中的某一个任务执行完成之后,返回一个pair,键值对是结果和任务序号。
 
复制代码
array<task<int>, 3> tasks = {
        create_task([]() -> int { return 88; }),
        create_task([]() -> int { return 42; }),
        create_task([]() -> int { return 99; })
    };
 
    // Select the first to finish.
    when_any(begin(tasks), end(tasks)).then([](pair<int, size_t> result)
    {
        cout << "First task to finish returns "
              << result.first
              << " and has index "
              << result.second<<endl;
    }).wait();
//output: First task to finish returns 42 and has index 1.
复制代码
 
 
总结:
  ppl和tbb两个并行运算库功能相似,如果需要跨平台则选择tbb,  否则选择ppl。ppl在任务调度上比tbb强大,tbb由于设计上的原因不能做到任务的连续执行以及任务的组合,但是tbb有跨平台的优势。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值