【问题标题】:SIMD array add for arbitrary array lengthsSIMD 数组添加任意数组长度
【发布时间】:2012-04-27 09:29:44
【问题描述】:

我正在通过使用矢量内在函数重写我的个人图像处理库来学习使用 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 指令进行所有添加?我想试试这个:

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

对于额外的元素,但这会导致未定义的行为吗? mask 应该保证实际上没有访问超出数组边界(我认为)。另一种方法是先做多余的元素,然后数组需要按n-n16对齐,这似乎不对。

还有其他更优化的模式,比如矢量化循环吗?

【问题讨论】:

  • 您可以确保在您的代码中,数组长度始终是 16 字节的倍数(尽管实际使用的元素可能更少),所以这个结语永远不会出现。但就速度而言,结语真的不重要。

标签: c arrays sse simd sse2


【解决方案1】:

一种选择是将数组填充为 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

但是如果不做一些分析就很难说。

您也可以在最后执行未对齐的加载/存储(假设您有超过 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);

【讨论】:

  • 在实践中,我会将掩码放在查找表中。你认为它仍然会比“结语”循环慢吗?
  • @reve_etrange:可能不会慢,但如果不测量这两种解决方案就很难知道。试一试。
  • 我会试一试。但它是合法的内存访问吗?由于mask一些 值可能导致数组边界冲突。
  • @reve_etrange: 存储基本上没问题,它是一个字节一个字节的,你仍然需要加载,所以你需要小心一点。如果地址是非法的,您将在加载或存储时遇到异常,但只要地址正确,只会触及受影响的字节。
  • @reve_etrange:存在这种风险,但这并不意味着必须填充所有内容,如果您在连续内存中有向量,则可以保证最后一个被填充是安全的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-31
  • 2017-10-05
相关资源
最近更新 更多