OpenMP -嵌套for循环在外部循环之前并行时变得更快.为什么
OpenMP - Nested for-loop becomes faster when having parallel before outer loop. Why?
我目前正在实现一个动态规划算法来解决背包问题。因此,我的代码有两个for循环,一个外部循环和一个内部循环。
从逻辑的角度来看,我可以并行化内部for循环,因为那里的计算是相互独立的。由于依赖关系,外部for循环不能并行化。
这是我的第一个方法:
for(int i=1; i < itemRows; i++){
int itemsIndex = i-1;
int itemWeight = integerItems[itemsIndex].weight;
int itemWorth = integerItems[itemsIndex].worth;
#pragma omp parallel for if(weightColumns > THRESHOLD)
for(int c=1; c < weightColumns; c++){
if(c < itemWeight){
table[i][c] = table[i-1][c];
}else{
int worthOfNotUsingItem = table[i-1][c];
int worthOfUsingItem = itemWorth + table[i-1][c-itemWeight];
table[i][c] = worthOfNotUsingItem < worthOfUsingItem ? worthOfUsingItem : worthOfNotUsingItem;
}
}
}
代码运行良好,算法正确地解决了问题。然后我在考虑优化这个,因为我不确定OpenMP的线程管理是如何工作的。我想在每次迭代中防止不必要的线程初始化,因此我在外部循环周围放置了一个外部并行块。
第二种方法:
#pragma omp parallel if(weightColumns > THRESHOLD)
{
for(int i=1; i < itemRows; i++){
int itemsIndex = i-1;
int itemWeight = integerItems[itemsIndex].weight;
int itemWorth = integerItems[itemsIndex].worth;
#pragma omp for
for(int c=1; c < weightColumns; c++){
if(c < itemWeight){
table[i][c] = table[i-1][c];
}else{
int worthOfNotUsingItem = table[i-1][c];
int worthOfUsingItem = itemWorth + table[i-1][c-itemWeight];
table[i][c] = worthOfNotUsingItem < worthOfUsingItem ? worthOfUsingItem : worthOfNotUsingItem;
}
}
}
}
这有一个不想要的副作用:并行块中的所有内容现在都将执行n次,其中n是可用内核的数量。我已经尝试使用pragmas single
和critical
来强制外部for循环在一个线程中执行,但是我不能通过多个线程计算内部循环,除非我打开一个新的并行块(但是那样就不会有速度上的增益)。但没关系,因为好处是:这不会影响结果。问题还是正确解决了。
现在奇怪的是:第二种方法比第一种方法快!
这是怎么回事?我的意思是,尽管外部for循环被计算n次(并行),内部for循环在n个内核中被分配n次,但它比第一种方法快,第一种方法只计算外部循环一次,并平均分配内部for循环的工作负载。
起初我在想:"嗯,是的,这可能是因为线程管理",但后来我读到OpenMP池的实例化线程,这将违背我的假设。然后我禁用了编译器优化(编译器标志- 0)来检查它是否与。但这并不影响测量。
你们谁能解释得更清楚一点?
测量的时间用于解决包含7500个物品的背包问题,最大容量为45000(创建7500x45000的矩阵,这远远超过代码中使用的THRESHOLD变量):
- 方法1:~0.88s
- 方法2:~0.52s
提前感谢,
phineliner
编辑:
测量一个更复杂的问题:问题增加2500项(从7500项增加到10000项)(由于内存原因,目前无法处理更复杂的问题)
- 方法1:~1.19s
- 方法二:~0.71s
EDIT2 :我误解了编译器的优化。这并不影响测量。至少我不能再现我之前测量到的差异。
让我们首先考虑一下代码在做什么。本质上你的代码是转换矩阵(二维数组),其中的行值依赖于前一行,但列的值是独立于其他列。让我选择一个更简单的例子
for(int i=1; i<n; i++) {
for(int j=0; j<n; j++) {
a[i*n+j] += a[(i-1)*n+j];
}
}
一种并行化的方法是像这样交换循环
方法1:#pragma omp parallel for
for(int j=0; j<n; j++) {
for(int i=1; i<n; i++) {
a[i*n+j] += a[(i-1)*n+j];
}
}
使用此方法,每个线程运行内部循环的i
的所有n-1
迭代,而j
的n/nthreads
迭代。这有效地并行处理列条。但是,这种方法对缓存非常不友好。
另一种可能是只并行处理内部循环。
方法2:for(int i=1; i<n; i++) {
#pragma omp parallel for
for(int j=0; j<n; j++) {
a[i*n+j] += a[(i-1)*n+j];
}
}
这实际上是并行地处理单行中的列,但每一行都是顺序的。i
的值仅由主线程运行。
另一种并行处理列但每行顺序的方法是:
方法3:#pragma omp parallel
for(int i=1; i<n; i++) {
#pragma omp for
for(int j=0; j<n; j++) {
a[i*n+j] += a[(i-1)*n+j];
}
}
在这个方法中,与方法1一样,每个线程运行在i
上的所有n-1
迭代。然而,这个方法在内部循环之后有一个隐式的屏障,它导致每个线程暂停,直到所有线程都完成一行,使得这个方法对每一行都是顺序的,就像方法2一样。
最好的解决方案是像方法1一样并行处理列条,但仍然是缓存友好的。这可以使用nowait
子句来实现。
#pragma omp parallel
for(int i=1; i<n; i++) {
#pragma omp for nowait
for(int j=0; j<n; j++) {
a[i*n+j] += a[(i-1)*n+j];
}
}
在我的测试中,nowait
子句没有多大区别。这可能是因为负载是均匀的(这就是为什么静态调度在这种情况下是理想的)。如果负载更少,nowait
可能会产生更大的差异。
以下是n=3000
在我的四核IVB系统GCC 4.9.2上以秒为单位的时间:
method 1: 3.00
method 2: 0.26
method 3: 0.21
method 4: 0.21
这个测试可能是内存带宽限制,所以我可以选择一个更好的情况下使用更多的计算,但是差异是足够显著的。为了消除由于创建线程池而产生的偏差,我没有先对其中一个方法进行计时就运行了它。
从时间上可以清楚地看出方法1是多么的非缓存友好。同样明显的是,方法3比方法2更快,nowait
在这种情况下几乎没有影响。
由于方法2和方法3都并行地处理一行中的列,但是顺序地处理行,因此可以期望它们的时间相同。那么它们为什么不同呢?让我做一些观察:
由于线程池,线程不会为方法2的外部循环的每次迭代创建和销毁,所以我不清楚额外的开销是什么。注意,OpenMP没有提到线程池。
方法3和方法2之间唯一的其他区别是,在方法2中只有主线程处理
i
,而在方法3中每个线程处理一个私有i
。但是对我来说,这似乎太微不足道了,无法解释方法之间的显著差异,因为方法3中的隐式障碍导致它们无论如何都同步,处理i
是一个增量和条件测试的问题。方法3并不比并行处理整条列的方法4慢,这说明方法2的额外开销都是在每次迭代
i
时离开和进入并行区域
所以我的结论是,要解释为什么方法2比方法3慢得多,需要研究线程池的实现。对于使用pthread的GCC,这可能可以通过创建一个线程池的玩具模型来解释,但我还没有足够的经验。
我认为原因很简单,因为您将#pragma omp parallel
置于外部作用域级别(第二个版本),因此调用线程的开销更少。
itemRows
时间调用线程创建,而在第二个版本中,您只调用创建一次。我试着重现一个简单的例子来说明这一点,使用启用了HT的4个线程:
#include <iostream>
#include <vector>
#include <algorithm>
#include <omp.h>
int main()
{
std::vector<double> v(10000);
std::generate(v.begin(), v.end(), []() { static double n{0.0}; return n ++;} );
double start = omp_get_wtime();
#pragma omp parallel // version 2
for (auto& el : v)
{
double t = el - 1.0;
// #pragma omp parallel // version 1
#pragma omp for
for (size_t i = 0; i < v.size(); i ++)
{
el += v[i];
el-= t;
}
}
double end = omp_get_wtime();
std::cout << " wall time : " << end - start << std::endl;
// for (const auto& el : v) { std::cout << el << ";"; }
}
根据您想要的版本注释/取消注释。如果你使用:-std=c++11 -fopenmp -O2
编译,你应该会看到版本2更快了。
Coliru Demo
Live Version 1 wall time : 0.512144
Live version 2 wall time : 0.333664
- C++我的数学有什么问题,为什么我的代码不能正确循环
- 我正在使用嵌套的while循环来解析具有多行的文本文件,但由于某种原因,它只通过第一行,我不知道为什么
- 为什么我的for循环不能正确获取argv
- 为什么在这个代码结束循环中没有得到结束
- 为什么我无法更改"set<set>"循环中的值<int>
- 为什么 const std::p air<K,V>& 在 std::map 上基于范围的 for 循环不起作用?
- 为什么我不能在 while 循环中创建线程?
- 为什么在C的循环中使用printf的Rust代码不显示输出,而在C++的循环中显示std::cout
- 当我在第一个循环中使用"auto"时,它工作正常,但是使用"int"它会给出错误,为什么?
- 循环中的条件:为什么每次都调用strlen(),而vector.size()只调用一次
- 为什么我的程序在for循环中k=0时返回垃圾值
- 为什么在递归中使用循环会产生意想不到的结果?
- 为什么在 while 循环中返回表达式不起作用
- 为什么我的 scanf() 没有在我的数组上迭代我的 for 循环?
- 而循环:简单的除法程序输出零,不明白为什么
- 为什么我在尝试在单向链表中打印元素时会出现这个永无止境的循环
- 为什么 GCC 不能假设 std::vector::size 在这个循环中不会改变?
- 为什么循环不会停止?C++
- 我的CLL程序在c++中不会停止循环!为什么
- (c++) For循环-为什么它这样做