c++ - 打开 MP 瓶颈问题
问题描述
我试图用下面的代码观察一个基本的基于 openMP 的并行性,
#include<stdio.h>
#include<omp.h>
#include<stdlib.h>
#include <time.h>
int main(){
long i;
long x[] = {0,0,0,0};
omp_set_num_threads(4);
clock_t time=clock();
#pragma omp parallel for
for(i=0;i<100000000;i++){
x[omp_get_thread_num()]++;
}
double time_taken = (double)(clock() - time) / CLOCKS_PER_SEC;
printf("%ld %ld %ld %ld %lf\n",x[0],x[1],x[2],x[3],time_taken);
}
现在,我使用的是四核 i5 处理器。我检查了 4 个不同的线程值。发现以下结果,
Set: omp_set_num_threads(1);
Out: 100000000 0 0 0 0.203921
Set: omp_set_num_threads(2);
Out: 50000000 50000000 0 0 0.826322
Set: omp_set_num_threads(3);
Out: 33333334 33333333 33333333 0 1.448936
Set: omp_set_num_threads(4);
Out: 25000000 25000000 25000000 25000000 1.919655
x
数组值是准确的。但是随着线程数量的增加,时间却出人意料地增加了。我无法得到这种现象背后的任何解释/理由。不知何故,omp_get_thread_num()
函数本质上是原子的?还是我错过的其他东西?
编译为,gcc -o test test.c -fopenmp
更新
因此,根据已接受答案中的建议,我将代码修改如下,
#include<stdio.h>
#include<omp.h>
#include<stdlib.h>
int main(){
long i, t_id, fact=1096;
long x[fact*4];
x[0]=x[fact]=x[2*fact]=x[3*fact]=0;
omp_set_num_threads(4);
double time = omp_get_wtime();
#pragma omp parallel for private(t_id)
for(i=0;i<100000000;i++){
t_id = omp_get_thread_num();
x[t_id*fact]++;
}
double time_taken = omp_get_wtime() - time;
printf("%ld %ld %ld %ld %lf\n",x[0],x[fact],x[2*fact],x[3*fact],time_taken);
}
现在,结果是可以理解的,
Set: omp_set_num_threads(1)
Out: 100000000 0 0 0 0.250205
Set: omp_set_num_threads(2)
Out: 50000000 50000000 0 0 0.154980
Set: omp_set_num_threads(3)
Out: 33333334 33333333 33333333 0 0.078874
Set: omp_set_num_threads(4)
Out: 25000000 25000000 25000000 25000000 0.061155
因此,它与接受的答案中解释的缓存行大小有关。看看那里得到答案。
解决方案
正如 gha.st 所指出的,您观察的原因是错误共享和clock
函数的属性。
因此x[omp_get_thread_num()]
,是一种反模式。当然,您可以通过在记忆中增加一个步幅来利用您的新知识。但这也会将特定于硬件的属性(即缓存行大小)编码到您的数据结构中。这会导致令人讨厌的代码难以理解并且仍然具有较差的性能可移植性。
惯用的解决方案是使用以下任一方法:
如果您只对聚合感兴趣,请使用
reduction
子句,即:long x = 0; #pragma omp parallel for reduction(+:x) for(i=0;i<100000000;i++){ x++; } // total sum is now in x
如果您需要线程中的单个值,只需使用一个
private
变量,最好是按范围隐式使用。或者,如果您需要从构造外部进行特定初始化,请使用firstprivate
.#pragma omp parallel { long local_x = 0; // implicitly private by scope! #pragma omp for for(i=0;i<100000000;i++) { local_x++; } // can now do something with the the sum of the current thread. }
如果您需要外部每个线程的结果,您可以使用第二种形式并编写一次结果:
#pragma omp parallel { long local_x = 0; // implicitly private by scope! #pragma omp for for(i=0;i<100000000;i++) { local_x++; } x[omp_get_thread_num()] = local_x; }
这并不是说您永远不需要在设计数据结构时考虑到错误共享。但它并不像你想象的那么普遍。
推荐阅读
- java - 如何将文本文件中的不同子类对象类型添加到父类类型的数组列表中?
- vba - 如何使用 MSXML2.XMLHTTP 和 VBA 进行身份验证?
- pandas - 我需要根据两列从 DF 中删除重复项并根据第三列返回带最小值和最大值的行
- android - 如何处理 RecyclerView 中的可变高度图像?
- c++ - 如何从cpp中的函数返回二维数组
- javascript - 使用未知/动态内容高度转换高度的最佳方法?
- r - 从德雷克图形可视化中排除从包中导入的函数?
- twitter-bootstrap - 如何获得此 UI 效果
- node.js - 在另一个系统上确认用户
- google-api - 无法为 GMB 业务订阅发布/订阅推送通知