如何使用SIMD加速XOR两块内存?

我想尽可能快地对两块内存进行异或,我如何使用SIMD来加速它?

我的原始代码如下:

void region_xor_w64( unsigned char *r1, /* Region 1 */ unsigned char *r2, /* Region 2 */ int nbytes) /* Number of bytes in region */ { uint64_t *l1; uint64_t *l2; uint64_t *ltop; unsigned char *ctop; ctop = r1 + nbytes; ltop = (uint64_t *) ctop; l1 = (uint64_t *) r1; l2 = (uint64_t *) r2; while (l1 < ltop) { *l2 = ((*l1) ^ (*l2)); l1++; l2++; } } 

我自己写了一个,但速度很快。

 void region_xor_sse( unsigned char* dst, unsigned char* src, int block_size){ const __m128i* wrd_ptr = (__m128i*)src; const __m128i* wrd_end = (__m128i*)(src+block_size); __m128i* dst_ptr = (__m128i*)dst; do{ __m128i xmm1 = _mm_load_si128(wrd_ptr); __m128i xmm2 = _mm_load_si128(dst_ptr); xmm2 = _mm_xor_si128(xmm1, xmm2); _mm_store_si128(dst_ptr, xmm2); ++dst_ptr; ++wrd_ptr; }while(wrd_ptr < wrd_end); } 

更重要的问题是为什么要手动完成。 你有一个古老的编译器,你认为你可以智取吗? 当你不得不手动编写SIMD指令时,那些美好的旧时代已经结束。 今天,在99%的情况下,编译器将为您完成这项工作,并且可能会做得更好。 另外,不要忘记每隔一段时间就会有越来越多的扩展指令集出现新架构。 所以问问自己一个问题 – 您是否希望为每个平台维护N个实施副本? 您是否希望不断测试您的实施以确保它值得维护? 最有可能的答案是否定的。

您唯一需要做的就是编写最简单的代码。 编译器将完成剩下的工作。 例如,以下是我编写函数的方法:

 void region_xor_w64(unsigned char *r1, unsigned char *r2, unsigned int len) { unsigned int i; for (i = 0; i < len; ++i) r2[i] = r1[i] ^ r2[i]; } 

有点简单,不是吗? 猜猜看,编译器正在生成使用MOVDQUPXOR执行128位XOR的代码,关键路径如下所示:

 4008a0: f3 0f 6f 04 06 movdqu xmm0,XMMWORD PTR [rsi+rax*1] 4008a5: 41 83 c0 01 add r8d,0x1 4008a9: f3 0f 6f 0c 07 movdqu xmm1,XMMWORD PTR [rdi+rax*1] 4008ae: 66 0f ef c1 pxor xmm0,xmm1 4008b2: f3 0f 7f 04 06 movdqu XMMWORD PTR [rsi+rax*1],xmm0 4008b7: 48 83 c0 10 add rax,0x10 4008bb: 45 39 c1 cmp r9d,r8d 4008be: 77 e0 ja 4008a0  

正如@Mysticial所指出的,上面的代码使用的是支持未对齐访问的指令。 那些比较慢。 但是,如果程序员可以正确地采用对齐访问,则可以让编译器了解它。 例如:

 void region_xor_w64(unsigned char * restrict r1, unsigned char * restrict r2, unsigned int len) { unsigned char * restrict p1 = __builtin_assume_aligned(r1, 16); unsigned char * restrict p2 = __builtin_assume_aligned(r2, 16); unsigned int i; for (i = 0; i < len; ++i) p2[i] = p1[i] ^ p2[i]; } 

编译器为上面的C代码生成以下内容(注意movdqa ):

 400880: 66 0f 6f 04 06 movdqa xmm0,XMMWORD PTR [rsi+rax*1] 400885: 41 83 c0 01 add r8d,0x1 400889: 66 0f ef 04 07 pxor xmm0,XMMWORD PTR [rdi+rax*1] 40088e: 66 0f 7f 04 06 movdqa XMMWORD PTR [rsi+rax*1],xmm0 400893: 48 83 c0 10 add rax,0x10 400897: 45 39 c1 cmp r9d,r8d 40089a: 77 e4 ja 400880  

明天,当我给自己买一台带有Haswell CPU的笔记本电脑时,编译器会生成一个代码,使用256位指令而不是相同代码的128位,这样我的矢量性能提高了两倍。 即使我不知道Haswell能够做到这一点,它也会这样做。 您不仅要了解该function,还要编写代码的另一个版本并花一些时间对其进行测试。

顺便说一下,您的实现中似乎也有一个错误,其中代码可以跳过数据向量中最多3个剩余字节。

无论如何,我建议您信任您的编译器并学习如何validation生成的内容(即熟悉objdump )。 下一个选择是更改编译器。 然后才开始考虑手动编写矢量处理指令。 或者你会度过一段美好的时光!

希望能帮助到你。 祝好运!