我有这样一个没有信息的嵌套循环(就像性能测试一样):
const int N = 300;
for (int num = 0; num < 10000; num++) {
for (int i=0; i<N; i++) {
for (int j=0; j<N; j++) {
arr[i][j] = brr[i][j];
crr[i][j] = arr[i][j] - brr[i][j];
sum1 += crr[i][j];
sum2 += arr[i][j];
}
}
}
经过的时间是
about 6 s
我尝试使用OpenMP并行化不同的循环。但我对我得到的结果非常困惑。
在第一步中,我将“parallel for”pragma仅用于第一个(最外层)循环:
#pragma omp parallel for schedule(static) reduction(+:sum1,sum2)
for (int num = 0; num < 10000; num++) {
for (int i=0; i<N; i++) {
for (int j=0; j<N; j++) {
arr[i][j] = brr[i][j];
crr[i][j] = arr[i][j] - brr[i][j];
sum1 += crr[i][j];
sum2 += arr[i][j];
}
}
}
经过的时间是(2个核心)
3.81
然后我尝试使用“collapse”子句(2个核心)并行化两个内部循环:
for (int num = 0; num < 10000; num++) {
#pragma omp parallel for collapse(2) schedule(static) reduction(+:sum1, sum2)
for (int i=0; i<N; i++) {
for (int j=0; j<N; j++) {
arr[i][j] = brr[i][j];
crr[i][j] = arr[i][j] - brr[i][j];
sum1 += crr[i][j];
sum2 += arr[i][j];
}
}
}
经过的时间是
3.76
这比之前的情况更快。我不明白这个的原因。
如果我使用这些内部循环的融合(这意味着在性能意义上更好),就像这样
#pragma omp parallel for schedule(static) reduction(+:sum1,sum2)
for (int n = 0; n < N * N; n++) {
int i = n / N; int j = n % N;
经过的时间是
5.53
这让我很困惑。在这种情况下性能更差,但通常人们建议融合循环以获得更好的性能。
好的,现在让我们尝试仅像这样(2核)并行化中间循环:
for (int num = 0; num < 10000; num++) {
#pragma omp parallel for schedule(static) reduction(+:sum1,sum2)
for (int i=0; i<N; i++) {
for (int j=0; j<N; j++) {
arr[i][j] = brr[i][j];
crr[i][j] = arr[i][j] - brr[i][j];
sum1 += crr[i][j];
sum2 += arr[i][j];
}
}
}
同样,性能变得更好:
3.703
最后一步 - 仅对最内层循环进行并行化(假设根据之前的结果,这将是最快的情况)(2个核心):
for (int num = 0; num < 10000; num++) {
for (int i=0; i<N; i++) {
#pragma omp parallel for schedule(static) reduction(+:sum1,sum2)
for (int j=0; j<N; j++) {
arr[i][j] = brr[i][j];
crr[i][j] = arr[i][j] - brr[i][j];
sum1 += crr[i][j];
sum2 += arr[i][j];
}
}
}
但是(惊讶!)经过的时间是
about 11 s
这比以前的情况要慢得多。我无法理解所有这一切的原因。
顺便说一下,我正在寻找类似的问题,我找到了添加
的建议#pragma omp parallel
在第一个循环之前(例如,在this和that个问题中)。但为什么这是正确的程序?如果我们放置
#pragma omp parallel#
在for循环之前意味着每个线程完全执行for循环,这是不正确的(多余的工作)。的确,我试图插入
#pragma omp parallel
在具有不同位置
的最外层循环之前#pragma omp parallel for
正如我在这里描述的那样,并且在通话情况下性能更差(此外,在最新的情况下,仅在最内层循环并行化时,答案也是不正确的(即“sum2”不同 - 因为存在竞争条件)。
我想知道这种性能的原因(可能原因是数据交换的时间大于每个线程上实际计算的时间,但这是最新的情况),哪种解决方案最正确之一。
编辑:我已经禁用了编译器的优化(通过$ -O0 $选项)并且结果仍然相同(除了最新示例中流逝的时间(当并行化最内层循环时)从11减少到8秒)。 编译器选项:
g++ -std=gnu++0x -fopenmp -O0 test.cpp
变量的定义:
unsigned int seed;
const int N = 300;
int main()
{
double arr[N][N];
double brr[N][N];
for (int i=0; i < N; i++) {
for (int j = 0; j < N; j++) {
arr[i][j] = i * j;
brr[i][j] = i + j;
}
}
double start = omp_get_wtime();
double crr[N][N];
double sum1 = 0;
double sum2 = 0;
答案 0 :(得分:0)
由于并行编程中的变量是在线程(核心)之间共享的,因此您应该考虑node has not been declared
如何采取行动。此时,您的代码可能会以processor cache-memory
执行,这可能会损害您的处理器性能。
在您的第一个并行代码中,您在第一个false-sharing
处致电#pragma omp for
,这意味着每个主题都有自己的for
和i
。与第2个和第3个(仅通过折叠区分)并行代码比较j
的第2个,这意味着每个for
都有自己的i
。这两个代码更好,因为每个线程/核心更频繁地命中j
cache-line
。第4个代码对于缓存处理器来说完全是灾难,因为没有什么可以在那里共享。
我建议您使用英特尔的PCM或PAPI来衡量您的代码,以便找到合适的分析师。
问候。
答案 1 :(得分:0)
最后一步 - 仅对最内层循环进行并行化(假设根据之前的结果,这将是最快的情况)(2个核心)
但是(惊讶!)经过的时间是:
about 11 s
这一点都不奇怪。并行块执行隐式障碍,甚至可以连接和创建线程(某些库可能使用线程池来降低线程创建的成本)。
最后,打开平行区域是昂贵的。你应该尽可能少地做这件事。线程将同时并行运行外部循环,但是一旦它们到达omp for
块,它将划分迭代空间,因此结果应该仍然是正确的(如果你是的话,你应该让你的程序检查一下不确定)。
为了测试性能,您应该始终运行实验来转换编译器优化,因为它们会对应用程序的行为产生严重影响(您不应该对未经优化的程序的性能做出假设,因为它们的问题可能已在优化期间得到解决)
当制作包含所有循环的单个并行块时,我的设置中的执行时间减半(使用2个线程从9.536开始,减少到4.757)。
omp for
块仍然会应用隐式障碍,在您的示例中不需要。在示例中添加nowait
子句会将执行时间减少另外一半:2.120秒。
从这一点开始,您现在可以尝试探索其他选项。
由于更好地使用内存层次结构和向量化,并行化中间循环可将执行时间减少到仅0.732秒。 L1缺失率从~29%降低到~0.3%。
使用两个最内层循环的折叠使用两个线程没什么大不了的(应该检查强缩放)。
在这种情况下,使用omp simd
之类的其他指令并不能提高性能,因为编译器确信它可以安全地对最内层循环进行矢量化。
#pragma omp parallel reduction(+:sum1,sum2)
for (int num = 0; num < 10000; num++) {
#pragma omp for schedule(static) nowait
for (int i=0; i<N; i++) {
for (int j=0; j<N; j++) {
arr[i][j] = brr[i][j];
crr[i][j] = arr[i][j] - brr[i][j];
sum1 += crr[i][j];
sum2 += arr[i][j];
}
}
}
注意:使用perf
:
$ perf stat -e cache-references,cache-misses -r 3 ./test