|
并行庫(kù)充分利用多核的優(yōu)勢(shì),通過并行運(yùn)算提高程序效率,本文主要介紹c++中兩個(gè)知名的并行庫(kù),一個(gè)是intel開發(fā)的TBB,一個(gè)是微軟開發(fā)的PPL。本文只介紹其基本的常用用法:并行算法和任務(wù)。
TBB(Intel? Threading Building Blocks )
TBB是intel用標(biāo)準(zhǔn)c++寫的一個(gè)開源的并行計(jì)算庫(kù)。它的目的是提升數(shù)據(jù)并行計(jì)算的能力,可以在他的官網(wǎng)上下載最新的庫(kù)和文檔。TBB主要功能:
并行算法
任務(wù)調(diào)度
并行容器
同步原語(yǔ)
內(nèi)存分配器
TBB并行算法
parallel_for:并行方式遍歷一個(gè)區(qū)間。
parallel_for(1, 20000, [](int i){cout << i << endl; });
parallel_for(blocked_range
{
for (size_t i = r.begin(); i != r.end(); ++i)
cout << i << endl;
});
parallel_do和parallel_for_each:將算法應(yīng)用于一個(gè)區(qū)間
vector
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,但是有區(qū)別。它先將區(qū)間自動(dòng)分組,對(duì)每個(gè)分組進(jìn)行聚合(accumulate)計(jì)算,每組得到一個(gè)結(jié)果,最后將各組的結(jié)果進(jìn)行匯聚(reduce)。這個(gè)算法稍微復(fù)雜一點(diǎn),parallel_reduce(range,identity,func,reduction),第一個(gè)參數(shù)是區(qū)間范圍,第二個(gè)參數(shù)是計(jì)算的初始值,第三個(gè)參數(shù)是聚合函數(shù),第四個(gè)參數(shù)是匯聚參數(shù)。
復(fù)制代碼
float ParallelSum(float array [], size_t n) {
return parallel_reduce(
blocked_range
0.f,
[](const blocked_range
return std::accumulate(r.begin(), r.end(), value);
},
std::plus
);
}
復(fù)制代碼
這個(gè)對(duì)數(shù)組求和的例子就是先自動(dòng)分組然后對(duì)各組中的元素進(jìn)行聚合累加,最后將各組結(jié)果匯聚相加。
parallel_pipeline:并行的管道過濾器
數(shù)據(jù)流經(jīng)過一個(gè)管道,在數(shù)據(jù)流動(dòng)的過程中依次要經(jīng)過一些過濾器的處理,其中有些過濾器可能會(huì)并行處理數(shù)據(jù),這時(shí)就可以用到并行的管道過濾器。舉一個(gè)例子,比如我要讀入一個(gè)文件,先將文件中的數(shù)字提取出來,再將提取出來的數(shù)字做一個(gè)轉(zhuǎn)換,最后將轉(zhuǎn)換后的數(shù)字輸出到另外一個(gè)文件中。其中讀文件和輸出文件不能并興去做,但是中間數(shù)字轉(zhuǎn)換的環(huán)節(jié)可以并行去做的。parallel_pipeline的原型:
parallel_pipeline( max_number_of_live_tokens,
make_filter
make_filter
make_filter
...
make_filter
第一個(gè)參數(shù)是最大的并行數(shù),我們可以通過&連接多個(gè)filter,這些filter是順序執(zhí)行的,前一個(gè)filter的輸出是下一個(gè)filter的輸入。
復(fù)制代碼
float RootMeanSquare( float* first, float* last ) {
float sum=0;
parallel_pipeline( /*max_number_of_live_token=*/16,
make_filter
filter::serial,
[&](flow_control& fc)-> float*{
if( first
return first++;
} else {
fc.stop();
return NULL;
}
}
) &
make_filter
filter::parallel,
[](float* p){return (*p)*(*p);}
) &
make_filter
filter::serial,
[&](float x) {sum+=x;}
)
);
return sqrt(sum);
}
復(fù)制代碼
第一個(gè)filter生成數(shù)據(jù)(如從文件中讀取數(shù)據(jù)等),第二個(gè)filter對(duì)產(chǎn)生的數(shù)據(jù)進(jìn)行轉(zhuǎn)換,第三個(gè)filter是對(duì)轉(zhuǎn)換后的數(shù)據(jù)做累加。其中第二個(gè)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
parallel_invoke:并行調(diào)用,并行調(diào)用多個(gè)函數(shù)
void f();
extern void bar(int);
void RunFunctionsInParallel() {
tbb::parallel_invoke(f, []{bar(2);}, []{bar(3);} );
}
TBB任務(wù)
task_group表示可以等待或者取消的任務(wù)集合
task_group g;
g.run([]{TestPrint(); });
g.run([]{TestPrint(); });
g.run([]{TestPrint(); });
g.wait();
PPL(Parallel Patterns Library)
PPL是微軟開發(fā)的并行計(jì)算庫(kù),它的功能和TBB是差不多的,但是PPL只能在windows上使用。二者在并行算法的使用上基本上是一樣的, 但還是有差異的。二者的差異:
parallel_reduce的原型有些不同,PPL的paraller_reduce函數(shù)多一個(gè)參數(shù),原型為parallel_reduce(begin,end,identity,func,reduction), 比tbb多了一個(gè)參數(shù),但是表達(dá)的意思差不多,一個(gè)是區(qū)間,一個(gè)是區(qū)間迭代器。
PPL中沒有parallel_pipeline接口。
TBB的task沒有PPL的task強(qiáng)大,PPL的task可以鏈?zhǔn)竭B續(xù)執(zhí)行還可以組合任務(wù),TBB的task則不行。
PPL任務(wù)的鏈?zhǔn)竭B續(xù)執(zhí)行then
復(fù)制代碼
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
*/
復(fù)制代碼
PPL任務(wù)的組合
1.when_all可以執(zhí)行一組任務(wù),所有任務(wù)完成之后將所有任務(wù)的結(jié)果返回到一個(gè)集合中。要求該組任務(wù)中的所有任務(wù)的返回值類型都相同。
復(fù)制代碼
array
{
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
{
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();
復(fù)制代碼
2.when_any任務(wù)組中的某一個(gè)任務(wù)執(zhí)行完成之后,返回一個(gè)pair,鍵值對(duì)是結(jié)果和任務(wù)序號(hào)。
復(fù)制代碼
array
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
{
cout << "First task to finish returns "
<< result.first
<< " and has index "
<< result.second<
}).wait();
//output: First task to finish returns 42 and has index 1.
復(fù)制代碼
總結(jié):
ppl和tbb兩個(gè)并行運(yùn)算庫(kù)功能相似,如果需要跨平臺(tái)則選擇tbb, 否則選擇ppl。ppl在任務(wù)調(diào)度上比tbb強(qiáng)大,tbb由于設(shè)計(jì)上的原因不能做到任務(wù)的連續(xù)執(zhí)行以及任務(wù)的組合,但是tbb有跨平臺(tái)的優(yōu)勢(shì)。
|
|
|