[英]Parallel for loop in openmp
我正在尝试并行化一个非常简单的 for 循环,但这是我很长时间以来第一次尝试使用 openMP。 我对运行时间感到困惑。 这是我的代码:
#include <vector>
#include <algorithm>
using namespace std;
int main ()
{
int n=400000, m=1000;
double x=0,y=0;
double s=0;
vector< double > shifts(n,0);
#pragma omp parallel for
for (int j=0; j<n; j++) {
double r=0.0;
for (int i=0; i < m; i++){
double rand_g1 = cos(i/double(m));
double rand_g2 = sin(i/double(m));
x += rand_g1;
y += rand_g2;
r += sqrt(rand_g1*rand_g1 + rand_g2*rand_g2);
}
shifts[j] = r / m;
}
cout << *std::max_element( shifts.begin(), shifts.end() ) << endl;
}
我编译它
g++ -O3 testMP.cc -o testMP -I /opt/boost_1_48_0/include
也就是说,没有“-fopenmp”,我得到了这些时间:
real 0m18.417s
user 0m18.357s
sys 0m0.004s
当我使用“-fopenmp”时,
g++ -O3 -fopenmp testMP.cc -o testMP -I /opt/boost_1_48_0/include
我得到了这些数字:
real 0m6.853s
user 0m52.007s
sys 0m0.008s
这对我来说没有意义。 如何使用八核只能使性能提高 3 倍? 我是否正确编码循环?
您应该对x
和y
使用 OpenMP reduction
条款:
#pragma omp parallel for reduction(+:x,y)
for (int j=0; j<n; j++) {
double r=0.0;
for (int i=0; i < m; i++){
double rand_g1 = cos(i/double(m));
double rand_g2 = sin(i/double(m));
x += rand_g1;
y += rand_g2;
r += sqrt(rand_g1*rand_g1 + rand_g2*rand_g2);
}
shifts[j] = r / m;
}
通过reduction
每个线程在x
和y
累积自己的部分和,最后将所有部分值相加以获得最终值。
Serial version:
25.05s user 0.01s system 99% cpu 25.059 total
OpenMP version w/ OMP_NUM_THREADS=16:
24.76s user 0.02s system 1590% cpu 1.559 total
见 - 超线性加速:)
因为这个问题被高度关注,所以我决定添加一点 OpenMP 背景来帮助那些访问它的人
#pragma omp parallel
使用一组threads
创建一个并行区域,其中每个线程执行parallel region
包含的整个代码块。 从OpenMP 5.1可以阅读更正式的描述:
当线程遇到并行构造时,会创建一组线程来执行并行区域 (..)。 遇到并行构造的线程成为新组的主线程,在新并行区域的持续时间内线程编号为零。 新团队中的所有线程,包括主线程,都执行该区域。 创建团队后,团队中的线程数在该并行区域的持续时间内保持不变。
#pragma omp parallel for
创建一个parallel region
(如前所述),并且该区域的threads
将使用default chunk size
和default schedule
(通常是static
分配它所包含的循环迭代。 但是请记住, default schedule
可能因OpenMP
标准的不同具体实现而异。
从OpenMP 5.1您可以阅读更正式的描述:
工作共享循环结构指定一个或多个相关循环的迭代将由团队中的线程在其隐式任务的上下文中并行执行。 迭代分布在执行工作共享循环区域绑定到的并行区域的团队中已经存在的线程中。
此外,
并行循环结构是一种快捷方式,用于指定包含具有一个或多个关联循环且没有其他语句的循环结构的并行结构。
或者非正式地, #pragma omp parallel for
是构造函数#pragma omp parallel
与#pragma omp for
。 在您的情况下,这意味着:
#pragma omp parallel for
for (int j=0; j<n; j++) {
double r=0.0;
for (int i=0; i < m; i++){
double rand_g1 = cos(i/double(m));
double rand_g2 = sin(i/double(m));
x += rand_g1;
y += rand_g2;
r += sqrt(rand_g1*rand_g1 + rand_g2*rand_g2);
}
shifts[j] = r / m;
}
将创建一组线程,并将最外层循环的迭代块分配给这些线程中的每一个。
为了使其更具说明性,使用4
线程, #pragma omp parallel for
chunk_size=1
和静态schedule
将导致类似的结果:
在代码方面,循环将转换为逻辑上类似于:
for(int i=omp_get_thread_num(); i < n; i+=omp_get_num_threads())
{
c[i]=a[i]+b[i];
}
omp_get_thread_num 例程返回当前组内调用线程的线程号。
返回当前团队中的线程数。 在程序的顺序部分 omp_get_num_threads 返回 1。
或者换句话说, for(int i = THREAD_ID; i < n; i += TOTAL_THREADS)
。 THREAD_ID
范围从0
到TOTAL_THREADS - 1
, TOTAL_THREADS
表示在并行区域上创建的团队线程总数。
掌握了这些知识,并查看您的代码,您可以看到变量“x”和“y”的更新存在竞争条件。 这些变量在线程之间共享并在并行区域内更新,即:
x += rand_g1;
y += rand_g2;
要解决此竞争条件,您可以使用 OpenMP 的缩减条款:
指定每个线程私有的一个或多个变量是并行区域末尾的归约操作的主题。
非正式地,reduce 子句将为每个线程创建变量 'x' 和 'y' 的私有副本,并在并行区域的末尾将所有这些 'x' 和 'y' 变量求和到原始变量中来自初始线程的“x”和“y”变量。
#pragma omp parallel for reduction(+:x,y)
for (int j=0; j<n; j++) {
double r=0.0;
for (int i=0; i < m; i++){
double rand_g1 = cos(i/double(m));
double rand_g2 = sin(i/double(m));
x += rand_g1;
y += rand_g2;
r += sqrt(rand_g1*rand_g1 + rand_g2*rand_g2);
}
shifts[j] = r / m;
}
您最多可以实现(!)是线性加速。 现在我不记得哪个是来自 linux 的时间,但我建议您使用 time.h 或(在 c++ 11 中)“chrono”并直接从程序中测量运行时间。 最好将整个代码打包成一个循环,运行 10 次,平均得到 prog 的大约运行时间。
此外,您在 imo 上遇到了 x,y 问题 - 这不符合并行编程中数据局部性的范式。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.