我有一個 TBB 應用程式的獨立示例,我在 2-NUMA 節點 CPU 上運行,它在動態陣列上重復執行簡單的向量加法。它用一個更復雜的例子重現了我遇到的一個問題。我試圖通過與task_arenas通過 TBB 的 NUMA API 鏈接到單獨的 NUMA 節點的 2 并行初始化資料來在可用的 NUMA 節點之間干凈地劃分計算。然后應該進行后續的并行執行,以便對計算其任務的 cpu 的本地資料執行記憶體訪問。一個控制示例使用一個簡單parallel_for的 astatic_partitioner來執行計算,而我的預期示例呼叫每個task_arena呼叫 a 的任務parallel_for計算指定區域的向量加法,即之前在相應的NUMA節點中初始化的動態競技場的一半。與控制示例相比,此示例執行矢量加法所需的時間總是兩倍。task_arena為呼叫演算法的 s 創建任務的開銷不可能是開銷parallel_for,因為性能下降僅在tbb::task_arena::constraints應用 s 時發生。誰能向我解釋發生了什么以及為什么這種性能損失如此嚴厲。當我為大學專案做這件事時,資源的方向也會有所幫助。
#include <iostream>
#include <iomanip>
#include <tbb/tbb.h>
#include <vector>
int main(){
std::vector<int> numa_indexes = tbb::info::numa_nodes();
std::vector<tbb::task_arena> arenas(numa_indexes.size());
std::size_t numa_nodes = numa_indexes.size();
for(unsigned j = 0; j < numa_indexes.size(); j ){
arenas[j].initialize( tbb::task_arena::constraints(numa_indexes[j]));
}
std::size_t size = 10000000;
std::size_t part_size = std::ceil((float)size/numa_nodes);
double * A = (double *) malloc(sizeof(double)*size);
double * B = (double *) malloc(sizeof(double)*size);
double * C = (double *) malloc(sizeof(double)*size);
double * D = (double *) malloc(sizeof(double)*size);
//DATA INITIALIZATION
for(unsigned k = 0; k < numa_indexes.size(); k )
arenas[k].execute(
[&](){
std::size_t local_start = k*part_size;
std::size_t local_end = std::min(local_start part_size, size);
tbb::parallel_for(static_cast<std::size_t>(local_start), local_end,
[&](std::size_t i)
{
C[i] = D[i] = 0;
A[i] = B[i] = 1;
}, tbb::static_partitioner());
});
//PARALLEL ALGORITHM
tbb::tick_count t0 = tbb::tick_count::now();
for(int i = 0; i<100; i )
tbb::parallel_for(static_cast<std::size_t>(0), size,
[&](std::size_t i)
{
C[i] = A[i] B[i];
}, tbb::static_partitioner());
tbb::tick_count t1 = tbb::tick_count::now();
std::cout << "Time 1: " << (t1-t0).seconds() << std::endl;
//TASK ARENA & PARALLEL ALGORITHM
t0 = tbb::tick_count::now();
for(int i = 0; i<100; i ){
for(unsigned k = 0; k < numa_indexes.size(); k ){
arenas[k].execute(
[&](){
for(unsigned i=0; i<numa_indexes.size(); i )
task_groups[i].wait();
task_groups[k].run([&](){
std::size_t local_start = k*part_size;
std::size_t local_end = std::min(local_start part_size, size);
tbb::parallel_for(static_cast<std::size_t>(local_start), local_end,
[&](std::size_t i)
{
D[i] = A[i] B[i];
});
});
});
}
t1 = tbb::tick_count::now();
std::cout << "Time 2: " << (t1-t0).seconds() << std::endl;
double sum1 = 0;
double sum2 = 0;
for(int i = 0; i<size; i ){
sum1 = C[i];
sum2 = D[i];
}
std::cout << sum1 << std::endl;
std::cout << sum2 << std::endl;
return 0;
}
性能:
for(unsigned j = 0; j < numa_indexes.size(); j ){
arenas[j].initialize( tbb::task_arena::constraints(numa_indexes[j]));
}
$ taskset -c 0,1,8,9 ./RUNME
Time 1: 0.896496
Time 2: 1.60392
2e 07
2e 07
無限制的性能:
$ taskset -c 0,1,8,9 ./RUNME
Time 1: 0.652501
Time 2: 0.638362
2e 07
2e 07
編輯:我實作了task_group在@AlekseiFedotov 的建議資源中找到的使用,但問題仍然存在。
uj5u.com熱心網友回復:
提供的使用 arenas 的示例的一部分與檔案“設定首選 NUMA 節點”部分中的示例不是一對一的匹配。
進一步查看方法的規范task_arena::execute(),我們可以發現它task_arena::execute()是一個阻塞 API,即在傳遞的 lambda 完成之前它不會回傳。
另一方面,該方法的規范task_group::run()表明它的方法是異步的,即立即回傳,而不是等待傳遞的函子完成。
我猜這就是問題所在。代碼在 arenas 中以串行方式一個接一個地執行兩個并行回圈。考慮仔細遵循檔案中的示例。
順便說一句,oneTBB 專案是 TBB 的改進版本,可以在這里找到。
編輯問題的編輯答案:
- 請參閱對問題的評論。
- 等待應該發生在提交作業之后,而不是之前。另外,不需要去另一個arena的任務組做loop內的等待,只需通過NUMA回圈提交作業
arena[i].execute( [i, &] { task_group[i].run( [i, &] { /*...*/ } ); } ),然后在另一個回圈中,等待task_group對應的每一個task_arena。
請注意我如何通過副本捕獲 NUMA 回圈迭代。否則,代碼可能會在 lambda 主體中參考錯誤的資料。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/425945.html
