SIMD数组添加任意数组长度

时间:2012-04-16 00:57:07

标签: c arrays sse simd sse2

我正在学习使用SIMD功能,通过使用矢量内在函数重写我的个人图像处理库。一个基本功能是简单的“数组+=”,即

void arrayAdd(unsigned char* A, unsigned char* B, size_t n) {
    for(size_t i=0; i < n; i++) { B[i] += A[i] };
}

对于任意数组长度,明显的SIMD代码(假设由16对齐)类似于:

size_t i = 0;
__m128i xmm0, xmm1;
n16 = n - (n % 16);
for (; i < n16; i+=16) {
    xmm0 = _mm_load_si128( (__m128i*) (A + i) );
    xmm1 = _mm_load_si128( (__m128i*) (B + i) );
    xmm1 = _mm_add_epi8( xmm0, xmm1 );
    _mm_store_si128( (__m128i*) (B + i), xmm1 );
}
for (; i < n; i++) { B[i] += A[i]; }

但是可以使用SIMD指令进行 all 添加吗?我想过尝试这个:

__m128i mask = (0x100<<8*(n - n16))-1;
_mm_maskmoveu_si128( xmm1, mask, (__m128i*) (B + i) );

对于额外的元素,但是会导致未定义的行为吗? mask应该保证在数组范围之外没有实际访问(我认为)。另一种方法是首先执行额外的元素,然后需要通过n-n16对齐数组,这似乎不正确。

是否存在另一种更优化的模式,如矢量化循环?

1 个答案:

答案 0 :(得分:4)

一种选择是将数组填充为16个字节的倍数。然后你可以进行128位加载/添加/存储,并根据你关注的点忽略结果。

对于大型数组,虽然是逐字节的开销&#34; epilog&#34;会很小。展开循环可以更好地提高性能,例如:

for (; i < n32; i+=32) {
    xmm0 = _mm_load_si128( (__m128i*) (A + i) );
    xmm1 = _mm_load_si128( (__m128i*) (B + i) );
    xmm2 = _mm_load_si128( (__m128i*) (A + i + 16) );
    xmm3 = _mm_load_si128( (__m128i*) (B + i + 16) );
    xmm1 = _mm_add_epi8( xmm0, xmm1 );
    xmm3 = _mm_add_epi8( xmm2, xmm3 );
    _mm_store_si128( (__m128i*) (B + i), xmm1 );
    _mm_store_si128( (__m128i*) (B + i + 16), xmm3 );
}
// Do another 128 bit load/add/store here if required

但如果不进行一些剖析,很难说。

你也可以在最后做一个未对齐的加载/存储(假设你有超过16个字节),虽然这可能不会产生很大的不同。例如。如果你有20个字节你做一个加载/存储来偏移0和另一个未对齐的加载/添加/存储(_mm_storeu_si128__mm_loadu_si128)偏移4。

您可以使用_mm_maskmoveu_si128,但需要将掩码放入xmm寄存器,并且示例代码无法正常工作。您可能希望将掩码寄存器设置为所有FF,然后使用移位来对齐它。在一天结束时,它可能比未对齐的加载/添加/存储更慢。

这就像是:

mask = _mm_cmpeq_epi8(mask, mask); // Set to all FF's
mask = _mm_srli_si128(mask, 16-(n%16)); // Align mask
_mm_maskmoveu_si128(xmm, mask, A + i);