SSE 添加的字符

Posted

技术标签:

【中文标题】SSE 添加的字符【英文标题】:SSE additions of chars 【发布时间】:2014-04-15 00:55:47 【问题描述】:

我有一个包含 0 或 1 的 16 个字符的向量,我想使用 SSE 添加每 4 个不重叠的元素。

没有矢量化的简化版代码如下所示

char a[16]=1,0,0,1 ,0,0,1,0, 0,1,0,0, 0,0,0,1;
char sum1 = a[0]  + a[1]  + a[2]  + a[3];
char sum2 = a[4]  + a[5]  + a[6]  + a[7];
char sum3 = a[8]  + a[9]  + a[10] + a[11];
char sum4 = a[12] + a[13] + a[14] + a[15];

在我的应用程序中,向量的长度远大于 16,但它始终是 16 的倍数。我使用其他 SSE 逻辑运算获得该向量,这些运算为我提供了良好的加速,所以我想知道我是如何可以矢量化这些添加。下面是完整代码,其中 vec1、vec2 和 vec3 具有相同的长度 n(16 的倍数),向量计数为 n/4。

void myfunc( const char *vec1, const char *vec2, char *vec3, int *counts, int n)
  __m128i *r1   = (__m128i*)vec1;
  __m128i *r2   = (__m128i*)vec2;
  char *a = vec3;
  char temp[16] __attribute__ ((aligned (16)));

  for ( int i = 0; i < n; i+=16, r1++, r2++, a+=16 ) 
    _mm_store_si128((__m128i*)a, _mm_and_si128(*r1, *r2));

    _mm_store_si128((__m128i*)temp, _mm_or_si128(*r1, *r2));

    char size = a[0]+a[1]+a[2]+a[3];
    if( size == 0 )
        memcpy(a, temp, 4*sizeof(char));
        counts[k]++;
    
    k++;

    size = a[4]+a[5]+a[6]+a[7];
    if( size == 0 )
        memcpy(a+4, temp+4, 4*sizeof(char));
        counts[k]++;
    
    k++;

    size = a[8]+a[9]+a[10]+a[11];
    if( size == 0 )
        memcpy(a+8, temp+8, 4*sizeof(char));
        counts[k]++;
    
    k++;

    size = a[12]+a[13]+a[14]+a[15];
    if( size == 0 )
        memcpy(a+12, temp+12, 4*sizeof(char));
        counts[k]++;
    
    k++;
  

任何帮助将不胜感激。

【问题讨论】:

您希望如何打包输出 sum1..sum4? uint8? uint32? @Nayuki 我只需要测试这些变量是否等于 0。在这种情况下,它们是字符(在我的第一篇文章中是 int) 这种情况下,可以测试每个32位字是否为0。当且仅当32位字为0时,4个字节之和为0。 避免将char 用于非文本数据。 char 可以是有符号或无符号的,具体取决于编译器甚至编译器选项。更好的选择是使用int8_tuint8_t 中的stdint.h/cstdint 如果您的字符只有 0x00 或 0x01,您还可以在每组四个字符上使用 __popcnt 内在函数,重新解释为 unsigned int 【参考方案1】:

您可以比较整数,而不是比较字节。将来自atempcounts 的四个整数加载到SSE 寄存器中(在下面的代码中将它们称为a4tmp4counts4)。然后您可以使用 SSE 一次处理四个整数。这假设 counts 是一个 int32 数组。

例如,假设 a4 = 0,3,0,4,counts4 = 1,2,3,4,tmp4 = 5,6,7,8。在下面的代码中 test 将是 -1, 0, -1, 0。从计数中减去它得到计数 = 2,2,4,4。 testtmp4 的逻辑与是 5,0,7,0。将其添加到 a4 得到 a4 = 5,3,7,4。这应该可以满足您的需求。

for ( int i = 0; i < n; i+=16, r1++, r2++, a+=16, k+=4 ) 
    _mm_store_si128((__m128i*)a, _mm_and_si128(*r1, *r2));
    _mm_store_si128((__m128i*)temp, _mm_or_si128(*r1, *r2));

    __m128i a4 = _mm_load_si128((__m128i*)a);
    __m128i tmp4 = _mm_load_si128((__m128i*)tmp);
    __m128i counts4 = _mm_load_si128((__m128i*)&counts[k]);

    __m128i test = _mm_cmpeq_epi32(_mm_set1_epi32(0), a4);
    a4 = _mm_add_epi32(a4, _mm_and_si128(tmp4,test));        
    counts4 = _mm_sub_epi32(counts4, test);

    _mm_store_si128((__m128i*)a, a4);
    _mm_store_si128((__m128i*)counts, counts4);        

【讨论】:

这仍然有一些冗余存储 -> 负载。但是,是的,这里的主要优化是,如果字节元素是无符号的,则测试整个 32 位的非零就足够了。

以上是关于SSE 添加的字符的主要内容,如果未能解决你的问题,请参考以下文章

使用 SSE 反转字符串

使用 x64 SSE / AVX 寄存器进行字符串反转

将 16 字节字符串与 SSE 进行比较

将 4 个 SSE 整数提取为 4 个字符

SIMD,SSE,AVX - 掩码 8 浮动无符号字符? [复制]

如何“删除” SSE 寄存器末尾的字节?