英特尔加载固有问题

Intel load intrinsic issue

这段代码的目的是为字符串str的每个字符减去键数组中的一个值。该程序的非矢量化版本对应于两个程序中的最后一个循环。 这段代码如何:

void decode(const char* key, int m, char* str) {
  int i; int n = strlen(str);
  __m128i k = _mm_loadu_si128((const __m128i*) key);
  for (int i = 0; i + 16 < n; i+=m) {
    __m128i s = _mm_loadu_si128((__m128i*) (str + i));
    s = _mm_sub_epi8(s, k);
    _mm_storeu_si128((__m128i*) (str + i), s);
  }
  for(; i<n; i++) str[i] -= key[i%m];
}

与此不同?

void decode(const char* key, int m, char* str) {
  int i, n = strlen(str);
  char keybuf[16] = { 0 };
  memcpy(keybuf, key, m);
  __m128i k = _mm_loadu_si128((__m128i*)keybuf);
  for (i=0; i+16 < n; i += m) {
    __m128i s = _mm_loadu_si128((__m128i*)(str+i));
    s = _mm_sub_epi8(s,k);
    _mm_storeu_si128((__m128i*)(str+i), s);
  }
  for (; i<n; i++) str[i] -= key[i % m]; }

如果没有内存复制,相同的代码将无法以相同的方式工作。 我正在使用 gcc -msse2 进行编译。 为什么需要内存拷贝?

不同之处在于,在第二种情况下,您只将 m 个字符加载到 keybuf 中,其余元素保持初始化为 0。这些额外的元素对 [=13] 没有影响=].

然而,在第一个版本中,您很可能在向量末尾有 non-zero 个元素,因为您盲目地从 key 加载所有 16 个元素,而不管密钥的实际长度。

要使第一个版本正常工作,您需要屏蔽掉 k 的最后 16 - m 个元素,强制它们为零,例如

const int8_t mask[32] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; 
__m128i k = _mm_loadu_si128((const __m128i*) key); // load 16 elements
k = _mm_and_si128(k, _mm_loadu_si128((const __m128i*)&mask[16 - m]));
                                                   // mask out final 16 - m elements

(注意:可能有更有效的屏蔽方法,但这是我能在短时间内想到的最好方法。它仍然比 memcpy 版本更有效,我会猜测。请参阅 this question and its answers 了解其他一些方法。)