我从一个更大的2D程序中提取了这个简单的成员函数,它所做的只是一个for循环访问三个不同的数组并进行数学运算(1D卷积)。我一直在测试使用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上使用gcc 4.9(这台机器上只有4个CPU),整个程序的执行效果差别很小。
使用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
答案 0 :(得分:2)
我有一些一般性意见:
<强> 1。在优化代码之前,请确保数据是16字节对齐的。这对于想要应用的优化非常重要。如果数据被分成3个部分,最好添加一些虚拟元素,使3个部分的起始地址全部为16字节对齐。通过这样做,CPU可以轻松地将数据加载到缓存行中。
<强> 2。在实现openMP 之前,确保简单函数已向量化。大多数情况下,使用AVX / SSE指令集应该可以为您提供2到8倍的单线程改进。对于您的情况非常简单:创建一个恒定的mm256寄存器并将其设置为值2,并将8个整数加载到3个mm256寄存器。使用Haswell处理器,可以一起完成一次加法和一次乘法。所以理论上,如果AVX管道可以填充,循环应该加速12倍!
第3。有时并行化会降低性能:现代CPU需要数百到数千个时钟周期才能预热,进入高性能状态并扩展频率。如果任务不够大,很可能在CPU预热之前完成任务,并且无法通过并行获得速度提升。并且不要忘记openMP也有开销:线程创建,同步和删除。另一种情况是内存管理不善。数据访问非常分散,所有CPU内核都处于空闲状态并等待来自RAM的数据。
我的建议:
你可能想尝试英特尔MKL,不要重新发明轮子。库优化到极端,没有浪费时钟周期。可以与串行库或并行版本链接,如果可以并行,则可以保证速度提升。