为什么这个 for 循环使用 OpenMP 没有更快?
Why is this for loop not faster using OpenMP?
我从一个更大的二维程序中提取了这个简单的成员函数,它所做的只是一个 for 循环,从三个不同的数组访问并进行数学运算(一维卷积)。我一直在测试使用 OpenMP 使这个特定功能更快:
void Image::convolve_lines()
{
const int *ptr0 = tmp_bufs[0];
const int *ptr1 = tmp_bufs[1];
const int *ptr2 = tmp_bufs[2];
const int width = Width;
#pragma omp parallel for
for ( int x = 0; x < width; ++x )
{
const int sum = 0
+ 1 * ptr0[x]
+ 2 * ptr1[x]
+ 1 * ptr2[x];
output[x] = sum;
}
}
如果我在 debian/wheezy amd64 上使用 gcc 4.7,则整个程序在 8 个 CPU 机器上的执行速度会慢很多。如果我在 debian/jessie amd64(这台机器上只有 4 个 CPU)上使用 gcc 4.9,整个程序的执行差别很小。
用time
比较:
单核运行:
$ ./test black.pgm out.pgm 94.28s user 6.20s system 84% cpu 1:58.56 total
多核运行:
$ ./test black.pgm out.pgm 400.49s user 6.73s system 344% cpu 1:58.31 total
其中:
$ head -3 black.pgm
P5
65536 65536
255
所以Width
在执行过程中被设置为65536
。
如果有关系,我正在使用 cmake 进行编译:
add_executable(test test.cxx)
set_target_properties(test PROPERTIES COMPILE_FLAGS "-fopenmp" LINK_FLAGS "-fopenmp")
并且CMAKE_BUILD_TYPE设置为:
CMAKE_BUILD_TYPE:STRING=Release
这意味着 -O3 -DNDEBUG
我的问题是,为什么这个 for
循环在使用多核时速度不快?数组上没有重叠,openmp 应该平分内存。我看不出瓶颈来自哪里?
编辑:如评论所述,我将输入文件更改为:
$ head -3 black2.pgm
P5
33554432 128
255
所以Width
现在在执行时设置为33554432
(应该考虑够了)。现在时间显示:
单核运行:
$ ./test ./black2.pgm out.pgm 100.55s user 5.77s system 83% cpu 2:06.86 total
多核 运行(由于某种原因 cpu% 始终低于 100%,这表明根本没有线程):
$ ./test ./black2.pgm out.pgm 117.94s user 7.94s system 98% cpu 2:07.63 total
我有一些一般性的意见:
1.在优化您的代码之前,请确保数据是 16 字节对齐的。 这对于想要应用的任何优化都非常重要。而如果数据分成3块,最好加入一些dummy元素,让3块的起始地址都是16字节对齐的。通过这样做,CPU 可以轻松地将您的数据加载到缓存行中。
2。在实现 openMP 之前确保简单函数被向量化。大多数情况下,使用 AVX/SSE 指令集应该会给您带来 2 到 8 倍的单线程改进。对于您的情况来说非常简单:创建一个常量 mm256 寄存器并将其设置为值 2,然后将 8 个整数加载到三个 mm256 寄存器。使用Haswell处理器,一加一乘可以一起完成。所以理论上,如果可以填充 AVX 管道,循环应该加速 12 倍!
3。有时并行化会降低性能 :现代 CPU 需要数百到数千个时钟周期来预热,进入高性能状态并提高频率。如果任务不够大,很可能任务在 CPU 预热之前完成,并且无法通过并行获得速度提升。不要忘记 openMP 也有开销:线程创建、同步和删除。另一种情况是内存管理不善。数据访问非常分散,所有 CPU 个核心都处于空闲状态并等待来自 RAM 的数据。
我的建议:
你可能想试试intel MKL,不要重新发明轮子。库优化到极致,没有时钟周期浪费。可以 link 使用串行库或并行版本,如果可以通过并行进行,则可以保证速度提升。
我从一个更大的二维程序中提取了这个简单的成员函数,它所做的只是一个 for 循环,从三个不同的数组访问并进行数学运算(一维卷积)。我一直在测试使用 OpenMP 使这个特定功能更快:
void Image::convolve_lines()
{
const int *ptr0 = tmp_bufs[0];
const int *ptr1 = tmp_bufs[1];
const int *ptr2 = tmp_bufs[2];
const int width = Width;
#pragma omp parallel for
for ( int x = 0; x < width; ++x )
{
const int sum = 0
+ 1 * ptr0[x]
+ 2 * ptr1[x]
+ 1 * ptr2[x];
output[x] = sum;
}
}
如果我在 debian/wheezy amd64 上使用 gcc 4.7,则整个程序在 8 个 CPU 机器上的执行速度会慢很多。如果我在 debian/jessie amd64(这台机器上只有 4 个 CPU)上使用 gcc 4.9,整个程序的执行差别很小。
用time
比较:
单核运行:
$ ./test black.pgm out.pgm 94.28s user 6.20s system 84% cpu 1:58.56 total
多核运行:
$ ./test black.pgm out.pgm 400.49s user 6.73s system 344% cpu 1:58.31 total
其中:
$ head -3 black.pgm
P5
65536 65536
255
所以Width
在执行过程中被设置为65536
。
如果有关系,我正在使用 cmake 进行编译:
add_executable(test test.cxx)
set_target_properties(test PROPERTIES COMPILE_FLAGS "-fopenmp" LINK_FLAGS "-fopenmp")
并且CMAKE_BUILD_TYPE设置为:
CMAKE_BUILD_TYPE:STRING=Release
这意味着 -O3 -DNDEBUG
我的问题是,为什么这个 for
循环在使用多核时速度不快?数组上没有重叠,openmp 应该平分内存。我看不出瓶颈来自哪里?
编辑:如评论所述,我将输入文件更改为:
$ head -3 black2.pgm
P5
33554432 128
255
所以Width
现在在执行时设置为33554432
(应该考虑够了)。现在时间显示:
单核运行:
$ ./test ./black2.pgm out.pgm 100.55s user 5.77s system 83% cpu 2:06.86 total
多核 运行(由于某种原因 cpu% 始终低于 100%,这表明根本没有线程):
$ ./test ./black2.pgm out.pgm 117.94s user 7.94s system 98% cpu 2:07.63 total
我有一些一般性的意见:
1.在优化您的代码之前,请确保数据是 16 字节对齐的。 这对于想要应用的任何优化都非常重要。而如果数据分成3块,最好加入一些dummy元素,让3块的起始地址都是16字节对齐的。通过这样做,CPU 可以轻松地将您的数据加载到缓存行中。
2。在实现 openMP 之前确保简单函数被向量化。大多数情况下,使用 AVX/SSE 指令集应该会给您带来 2 到 8 倍的单线程改进。对于您的情况来说非常简单:创建一个常量 mm256 寄存器并将其设置为值 2,然后将 8 个整数加载到三个 mm256 寄存器。使用Haswell处理器,一加一乘可以一起完成。所以理论上,如果可以填充 AVX 管道,循环应该加速 12 倍!
3。有时并行化会降低性能 :现代 CPU 需要数百到数千个时钟周期来预热,进入高性能状态并提高频率。如果任务不够大,很可能任务在 CPU 预热之前完成,并且无法通过并行获得速度提升。不要忘记 openMP 也有开销:线程创建、同步和删除。另一种情况是内存管理不善。数据访问非常分散,所有 CPU 个核心都处于空闲状态并等待来自 RAM 的数据。
我的建议:
你可能想试试intel MKL,不要重新发明轮子。库优化到极致,没有时钟周期浪费。可以 link 使用串行库或并行版本,如果可以通过并行进行,则可以保证速度提升。