簡體   English   中英

如何使用 SIMD 加速 XOR 兩個內存塊?

[英]How can I use SIMD to accelerate XOR two blocks of memory?

我想盡快對兩個內存塊進行異或,如何使用 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 位異或的代碼,關鍵路徑如下所示:

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 <region_xor_w64+0x40>

正如@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 <region_xor_w64+0x20>

明天,當我給自己買一台配備 Haswell CPU 的筆記本電腦時,編譯器會為我生成一個代碼,該代碼使用 256 位指令而不是來自相同代碼的 128 位指令,從而使我的向量性能提高一倍。 即使我不知道 Haswell 有能力,它也會這樣做。 您不僅必須了解該功能,還必須編寫另一個版本的代碼並花一些時間對其進行測試。

順便說一下,您的實現中似乎也有一個錯誤,其中代碼最多可以跳過數據向量中剩余的 3 個字節。

無論如何,我建議您信任您的編譯器並學習如何驗證生成的內容(即熟悉objdump )。 下一個選擇是更改編譯器。 然后才開始考慮手動編寫向量處理指令。 否則你會過得很糟糕!

希望能幫助到你。 祝你好運!

由於區域的大小是按值傳遞的,為什么代碼不是:

void region_xor_w64(unsigned char *r1, unsigned char *r2, unsigned int i)
{
    while (i--)
        r2[i] = r1[i] ^ r2[i];
}

甚至:

void region_xor_w64(unsigned char *r1, unsigned char *r2, unsigned int i)
{
    while (i--)
        r2[i] ^= r1[i];
}

如果傾向於前進(“向上內存”)和使用指針,則:

void region_xor_w64(unsigned char *r1, unsigned char *r2, unsigned int i)
{
    while (i--)
        *r2++ ^= *r1++;
}

好吧,如果intel寧願向前走,而寧願使用指針操作而不是索引,那么:

void region_xor_w64(unsigned char *r1, unsigned char *r2, unsigned int i)
{
    while (i--)
        *r2++ ^= *r1++;
}

麥克風

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM