rev*_*nge 6 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] };
}
Run Code Online (Sandbox Code Playgroud)
对于任意数组长度,明显的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]; }
Run Code Online (Sandbox Code Playgroud)
但是可以使用SIMD指令进行所有添加吗?我想过尝试这个:
__m128i mask = (0x100<<8*(n - n16))-1;
_mm_maskmoveu_si128( xmm1, mask, (__m128i*) (B + i) );
Run Code Online (Sandbox Code Playgroud)
对于额外的元素,但会导致未定义的行为吗?该mask应保证没有访问过数组边界(我认为)实际作出.另一种方法是首先执行额外的元素,但是数组需要对齐n-n16,这似乎不正确.
是否有另一种更优化的模式,如矢量化循环?
一种选择是将数组填充为 16 字节的倍数。然后您可以执行 128 位加载/添加/存储,并在您关心的点之后简单地忽略结果。
对于大型数组,尽管逐字节“epilog”的开销将非常小。展开循环可能会更多地提高性能,例如:
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
Run Code Online (Sandbox Code Playgroud)
但是如果不做一些分析就很难说。
您还可以在最后执行未对齐的加载/存储(假设您有 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);
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1624 次 |
| 最近记录: |