C++ OpenMP奇怪行为-性能差异
我想使用OpenMP加速图像处理代码,我在代码中发现了一些奇怪的行为。我使用Visual Studio 2019,我也尝试了英特尔C++编译器,结果相同。 我不知道为什么OpenMP的代码在某些情况下比在其他情况下慢得多。例如,函数C++ OpenMP奇怪行为-性能差异,c++,openmp,C++,Openmp,我想使用OpenMP加速图像处理代码,我在代码中发现了一些奇怪的行为。我使用Visual Studio 2019,我也尝试了英特尔C++编译器,结果相同。 我不知道为什么OpenMP的代码在某些情况下比在其他情况下慢得多。例如,函数divideImageDataWithParam()或copyFirstPixelOnRow()和copyFirstPixelOnRowUsingTSize()之间的差异,使用struct TSize作为图像数据大小的参数。为什么boxFilterRow()和boxF
divideImageDataWithParam()
或copyFirstPixelOnRow()
和copyFirstPixelOnRowUsingTSize()
之间的差异,使用struct TSize
作为图像数据大小的参数。为什么boxFilterRow()
和boxFilterRow\u OpenMP()
的性能如此不同,为什么程序中半径大小不同
我为这个小测试项目创建了github存储库:
以下是总结的所有结果:
我没有找到任何解释为什么会发生这种情况,或者我做错了什么。
谢谢你的帮助
我正在研究更快的盒子过滤器和其他图像处理算法
typedef intptr_t int_t;
struct TSize
{
int_t width;
int_t height;
};
void divideImageDataWithParam(
const unsigned char * src, int_t srcStep, unsigned char * dst, int_t dstStep, TSize size, int_t param)
{
for (int_t y = 0; y < size.height; y++)
{
for (int_t x = 0; x < size.width; x++)
{
dst[y*dstStep + x] = src[y*srcStep + x]/param;
}
}
}
void divideImageDataWithParam_OpenMP(
const unsigned char * src, int_t srcStep, unsigned char * dst, int_t dstStep, TSize size, int_t param, bool parallel)
{
#pragma omp parallel for if(parallel)
for (int_t y = 0; y < size.height; y++)
{
for (int_t x = 0; x < size.width; x++)
{
dst[y*dstStep + x] = src[y*srcStep + x]/param;
}
}
}
32bit 64bit
336.906ms 344.251ms divideImageDataWithParam
1832.120ms 6395.861ms divideImageDataWithParam_OpenMP single-thread parallel=false
387.152ms 1204.302ms divideImageDataWithParam_OpenMP multi-threaded parallel=true
英特尔C++ 19:
32bit 64bit
15.162ms 8.927ms divideImageDataWithParam
266.646ms 294.134ms divideImageDataWithParam_OpenMP single-threaded parallel=false
239.564ms 1195.556ms divideImageDataWithParam_OpenMP multi-threaded parallel=true
英特尔VTune放大器的屏幕截图,其中使用parallel=false的param_OpenMP()分割图像数据占用指令mov到dst内存的大部分时间。
使用编译器优化可以解释此行为:启用时,
divideImageDataWithParam
顺序代码将接受一系列优化(循环展开、矢量化等)divideImageDataWithParam\u OpenMP
并行代码可能不受此影响,因为在编译器勾勒出平行区域的过程之后,它肯定没有特征化
如果在没有优化的情况下编译相同的代码,您会发现顺序版本的运行时版本与只有一个线程的并行版本非常相似
在这种情况下,并行版本的最大加速比受到原始工作负载划分的限制,而没有进行优化。这种情况下的优化需要手动写入。648trindade是正确的;这与openmp无法完成的优化有关。但它不是循环展开或向量化,而是允许智能替换的内联 让我解释一下:整数除法非常慢(64位
IDIV
:~40-100个周期)。因此,只要有可能,人们(和编译人员)就尽量避免分裂。你可以使用的一个技巧是用乘法和移位代替除法。只有当除数在编译时已知时,这才有效。这种情况是因为函数divideImageDataWithParam
是内联的,并且PARAM是已知的。您可以用\uuu declspec(noinline)
在其前面加上前缀来验证这一点。你会得到你期望的时间
openmp并行化不允许这种技巧,因为函数不能内联,因此编译时不知道param
,并且生成了昂贵的IDIV
-指令
divideImageDataWithParam的编译器输出(WIN10,MSVC2017,x64):
0x7ff67d151480 movzx ecx,字节ptr[r10+r8]
0x7ff67d151485 mov rax,r12
0x7ff67d151488 mul rax,rcx 1)请记住在启用编译器优化的情况下构建。2) OpenMP(或任何类型的并行化)不是一个灵丹妙药,您的程序中必须有足够的并行工作可用,以消除创建多个线程、执行同步等的开销。否则你只是在放慢速度。3) 在尝试并行化代码时,要注意优先级反转、错误共享、活锁、死锁、竞争条件、同步等等。有许多潜在的陷阱。4) 不同供应商的OpenMP库性能也不同。好吧,我理解,但是在这个简单的代码中,如果使用#pragma omp,并且如果并行设置为false,那么与不使用pragma omp的代码相比,如何获得20倍的处理时间(64位)。做这个简单的任务可能有点尴尬。你发现了吗☆ __限制dst没有什么区别?如何比较优化报告与英特尔编译器和比较英特尔和微软OMP库与MSVC构建?用英特尔OMP库,我不能想象OMPPoSt=内核和减少NToX不会显示有趣的结果。关键字Sy-Stand限制DST没有区别,我把结果来自英特尔C++编译器,它仍然是比不使用OpenMP的单线程速度慢,使用多线程OpenMP时更慢。函数divideImageDataWithParam(param=1)真的很奇怪。还请记住,在这种情况下,使用静态循环调度程序(schedule(static)
)可以带来一些性能改进,因为迭代之间的工作负载没有变化。默认情况下,OpenMP运行时通常使用动态调度程序。
32bit 64bit
15.162ms 8.927ms divideImageDataWithParam
266.646ms 294.134ms divideImageDataWithParam_OpenMP single-threaded parallel=false
239.564ms 1195.556ms divideImageDataWithParam_OpenMP multi-threaded parallel=true
0x7ff67d151480 <+ 336> movzx ecx,byte ptr [r10+r8]
0x7ff67d151485 <+ 341> mov rax,r12
0x7ff67d151488 <+ 344> mul rax,rcx <------- multiply
0x7ff67d15148b <+ 347> shr rdx,3 <------- shift
0x7ff67d15148f <+ 351> mov byte ptr [r8],dl
0x7ff67d151492 <+ 354> lea r8,[r8+1]
0x7ff67d151496 <+ 358> sub r9,1
0x7ff67d15149a <+ 362> jne test!main+0x150 (00007ff6`7d151480)
0x7ff67d151210 <+ 192> movzx eax,byte ptr [r10+rcx]
0x7ff67d151215 <+ 197> lea rcx,[rcx+1]
0x7ff67d151219 <+ 201> cqo
0x7ff67d15121b <+ 203> idiv rax,rbp <------- idiv
0x7ff67d15121e <+ 206> mov byte ptr [rcx-1],al
0x7ff67d151221 <+ 209> lea rax,[r8+rcx]
0x7ff67d151225 <+ 213> mov rdx,qword ptr [rbx]
0x7ff67d151228 <+ 216> cmp rax,rdx
0x7ff67d15122b <+ 219> jl test!divideImageDataWithParam$omp$1+0xc0 (00007ff6`7d151210)
void divideImageDataWithParam(
const unsigned char * src, int_t srcStep, unsigned char * dst, int_t dstStep, TSize size, int_t param)
{
uint8_t tbl[256];
for(int i = 0; i < 256; i++) {
tbl[i] = i / param;
}
for (int_t y = 0; y < size.height; y++)
{
for (int_t x = 0; x < size.width; x++)
{
dst[y*dstStep + x] = tbl[src[y*srcStep + x]];
}
}
}