请考虑以下简单代码,用于汇总parallel for
循环中的值:
int nMaxThreads = omp_get_max_threads();
int nTotalSum = 0;
#pragma omp parallel for num_threads(nMaxThreads) \
reduction(+:nTotalSum)
for (int i = 0; i < 4; i++)
{
nTotalSum += i;
cout << omp_get_thread_num() << ": nTotalSum is " << nTotalSum << endl;
}
当我在双核机器上运行时,我得到的输出是
0: nTotalSum is 0
0: nTotalSum is 1
1: nTotalSum is 2
1: nTotalSum is 5
这告诉我,关键部分,即nTotalSum
的更新,正在每个循环上执行。这似乎是一种浪费,当所有每个线程都要做的是计算它所添加的值的“局部”总和,然后在它完成后用这个“局部和”更新nTotalSum
。
我对输出的解释是否正确,如果是,我怎样才能提高效率?注意我尝试了以下内容:
#pragma omp parallel for num_threads(nMaxThreads) \
reduction(+:nTotalSum)
int nLocalSum = 0;
for (int i = 0; i < 4; i++)
{
nLocalSum += i;
}
nTotalSum += nLocalSum;
...但是编译器抱怨说它在for
语句之后期待pragma omp parallel for
循环......
答案 0 :(得分:2)
每个OMP线程都有自己的nTotalSum
副本。在OMP部分的末尾,这些被组合回原始的nTotalSum
。您看到的输出来自在一个线程中运行循环迭代(0,1),以及在另一个线程中运行(2,3)。如果在循环结束时输出nTotalSum
,则应该看到预期的结果为6。
在nLocalSum
示例中,将nLocalSum
的声明移至#pragma omp
行之前。 for
循环必须紧跟在编译指示之后的行上。
答案 1 :(得分:2)
您的输出实际上并不表示循环期间的关键部分。每个线程都有自己的零初始化副本,线程0在i = 0,1
上工作,线程1在i = 2,3
上工作。最后,OpenMP负责将本地副本添加到原始副本中。
除非您有具体证据证明您可以更有效地执行此操作,否则您不应尝试自己实施。请参阅示例this question / answer。
如果将parallel
/ for
拆分为两个指令,您的手动版本就可以使用:
int nTotalSum = 0;
#pragma omp parallel
{
// Declare the local variable it here!
// Then it's private implicitly and properly initialized
int localSum = 0;
#pragma omp for
for (int i = 0; i < 4; i++) {
localSum += i;
cout << omp_get_thread_num() << ": nTotalSum is " << nTotalSum << endl;
}
// Do not forget the atomic, or it would be a race condition!
// Alternative would be a critical, but that's less efficient
#pragma omp atomic
nTotalSum += localSum;
}
我认为您的OpenMP实施可能就是这样做的。
答案 2 :(得分:0)
来自我在openmp书中的并行编程:
还原子句可能更难以理解,具有私有和共享存储行为。 reduction属性用于作为算术缩减目标的对象。这在许多应用程序中都很重要...减少允许它有效地由编译器实现...这是一个常见的操作,openmp具有简化数据范围子句只是为了处理它们...最常见的例子是最终求和并行构造末尾的临时局部变量。
修正你的第二个例子:
total_sum = 0; /* do all variable initialization prior to omp pragma */
#pragma omp parallel for \
private(i) \
reduction(+:total_sum)
for (int i = 0; i < 4; i++)
{
total_sum += i; /* you used nLocalSum here */
}
#pragma omp end parallel for
/* at this point in the code,
all threads will have done your `for` loop where total_sum is local to each thread,
openmp will then '+" together the values in `total_sum` coming from each thread because we used reduction,
do not do an explicit nTotalSum += nLocalSum after the omp for loop, it's not needed the reduction clause takes care of this
*/
在您的第一个示例中,我不确定您使用#pragma omp parallel for num_threads(nMaxThreads) reduction(+:nTotalSum)
num_threads(nMaxThreads)
正在做什么。但我怀疑这种奇怪的输出可能是由打印缓冲引起的。
在任何情况下,如果使用得当,减少条款非常有用并且非常有效。在更复杂,更现实的例子中,这将更加明显。
您发布的示例非常简单,它不会显示还原子句的有用性,严格来说就是您的示例,因为所有线程都在进行总和最多效率这样做的方法只是让 如果使用关键指令将会有效。total_sum
成为并行部分中的共享变量,并让所有线程都加入其中。最后,答案仍然是正确的。