我的程序中有以下代码,我想使用OpenMP加速它。
...
for(i=curr_index; i < curr_index + rx_size; i+=2){
int64_t tgt = rcvq[i];
int64_t src = rcvq[i+1];
if (!TEST(tgt)) {
pred[tgt] = src;
newq[newq_count++] = tgt;
}
}
目前,我的版本如下:
...
chunk = rx_sz / omp_nthreads;
#pragma omp parallel for num_threads(omp_nthreads)
for (ii = 0; ii < omp_nthreads; ii++) {
int start = curr_index + ii * chunk;
for (index = start; index < start + chunk; index +=2) {
int64_t tgt = rcvq[index];
int64_t src = rcvq[index+1];
if (!TEST(tgt)) {
pred[tgt] = src;
#pragma omp critical
newq[newq_count++] = tgt;
}
}
}
当我运行OpenMP版本时,与原始版本相比,我发现性能大幅下降。我认为问题可能是因为“omp critical”阻止了并行处理。我想知道我的代码可以增强什么,所以我可以在串行版本上获得更好的性能。在代码中,rx_sz始终是omp_nthreads的倍数。
答案 0 :(得分:3)
是的,关键部分限制了您的表现。您应该在本地为每个线程收集结果,然后合并它们。
size_t newq_offset = 0;
#pragma omp parallel
{
// Figure out something clever here...
const size_t max_newq_per_thread = max_newq / omp_get_num_threads();
int64_t* local_newq = malloc(max_results_per_thread * sizeof(int64_t));
size_t local_newq_count = 0;
#pragma omp parallel for
for (i=curr_index; i < curr_index + rx_size; i+=2)
int64_t tgt = rcvq[2*index];
int64_t src = rcvq[2*index+1];
if (!TEST(tgt)) {
pred[tgt] = src;
local_newq_count++;
assert(local_newq_count < max_newq_per_thread);
local_newq[local_newq_count] = tgt;
}
}
int local_offset;
#pragma omp atomic capture
{
local_offset = offset;
offset += local_newq_count;
}
for (size_t i = 0; i < counter; i++)
{
res_global[i + local_offset] = res[i];
}
}
使用这种方法,所有线程在合并时并行工作,atomic capture
上只有最小的争用。请注意,您也可以创建一个atomic capture
的简单版本,这个版本比关键部分更有效,但仍然会很快成为瓶颈:
size_t newq_count_local;
#pragma omp atomic capture
newq_count_local = newq_count++;
newq[newq_count_local] = tgt;
newq
内无法保证订单 critical
- 版本错误,因为index
(在外部作用域中定义)在线程之间隐含地共享。rcvq
中有没有重复项。否则,您会在pred[tgt] = src;
上遇到竞争条件。pragma omp for
循环。另一个答案是正确的。但是,它是C ++,而不是标记为C.使用std::vector<std::vector<>>
时,还存在一个微妙但重要的性能问题。通常,矢量用三个指针实现,总共24个字节。在push_back
时,其中一个指针递增。这意味着,a)来自多个线程的本地向量的指针驻留在同一缓存行上,并且b)对每个成功的TEST
,push_back
读取和写入由其他线程使用的缓存行螺纹(或多个)。此缓存行必须始终在内核之间移动,这极大地限制了此方法的可伸缩性。这称为虚假共享。
我根据另一个答案实施了一个小测试,给出了以下表现:
0.99 s
- 单线程1.58 s
- 同一套接字的两个相邻核心上的两个线程2.13 s
- 两个不同套接字核心上的两个线程0.99 s
- 两个线程共享一个核心0.62 s
- 两个套接字上的24个线程以上C版本的规模要好得多:
0.46 s
- 单线程(不是真正可比的C vs C ++)0.24 s
- 两个帖子0.04 s
- 24个帖子答案 1 :(得分:2)
我非常肯定omp关键部分会限制你的表现。
我建议您将结果收集到单独的缓冲区/向量中,并在并行处理完成后合并它们(当然,如果顺序对您无关紧要)
vector<vector<int64_t>> res;
res.resize(num_threads);
#pragma omp parallel for
for (index = 0; index < rx_sz/2; ++index) {
int64_t tgt = rcvq[2*index];
int64_t src = rcvq[2*index+1];
if (!TEST(tgt)) {
pred[tgt] = src;
res[omp_get_thread_num()].push_back(tgt);
}
}
// Merge all res vectors if needed