执行 sobel 操作后,我有以下代码:
short* tempBufferVert = new short[width * height];
ippiFilterSobelVertBorder_8u16s_C1R(pImg, width, tempBufferVert, width * 2, dstSize, IppiMaskSize::ippMskSize3x3, IppiBorderType::ippBorderConst, 0, pBufferVert);
for (int i = 0; i < width * height; i++)
tempBufferVert[i] >>= 2;
令人沮丧的是,位移位是所有操作中最长的,IPP sobel 非常优化,它比我愚蠢的位移运行得更快。如何优化位移,或者是否有 IPP 或其他选项(AVX?)对整个内存执行位移(但与>>= 在 Visual Studio 实现上执行的短字符有关)
C++优化器使用基于迭代器的循环比使用索引循环的性能要好得多。
这是因为编译器可以假设地址算术在索引溢出时的工作方式。为了在数组中使用索引时做出相同的假设,您必须碰巧为索引选择正确的数据类型。
移位代码可以表示为:
void shift(short* first, short* last, int bits)
{
while (first != last) {
*first++ >>= bits;
}
}
int test(int width, int height)
{
short* tempBufferVert = new short[width * height];
shift(tempBufferVert, tempBufferVert + (width * height), 2);
}
哪个(启用正确的优化)将被矢量化:https://godbolt.org/g/oJ8Boj
注意循环的中间是如何变成的:
.L76:
vmovdqa ymm0, YMMWORD PTR [r9+rdx]
add r8, 1
vpsraw ymm0, ymm0, 2
vmovdqa YMMWORD PTR [r9+rdx], ymm0
add rdx, 32
cmp rsi, r8
ja .L76
lea rax, [rax+rdi*2]
cmp rcx, rdi
je .L127
vzeroupper
请确保您在编译时启用了优化(例如 -O3
),然后检查编译器是否正在自动矢量化右移位循环。如果不是,那么使用 SSE 您可能会获得显着的改进:
#include <emmintrin.h> // SSE2
for (int i = 0; i < width * height; i += 8)
{
__m128i v = _mm_loadu_si128((__m128i *)&tempBufferVert[i]);
v = _mm_srai_epi16(v, 2); // v >>= 2
_mm_storeu_si128((__m128i *)&tempBufferVert[i], v);
}
(注意:假设width*height
是 8 的倍数。
通过一些循环展开和/或使用 AVX2,您可能会做得更好,但这可能足以满足您的需求。