簡體   English   中英

對於memcmp,SSE4.2字符串指令比SSE2快多少?

[英]How much faster are SSE4.2 string instructions than SSE2 for memcmp?

這是我的代碼匯編程序

你能用c ++嵌入它並檢查SSE4嗎? 速度快

我非常希望看到如何進入SSE4的發展。 或者根本不擔心他? 我們檢查一下(我沒有SSSE3以上的支持)

{ sse2 strcmp WideChar 32 bit }
function CmpSee2(const P1, P2: Pointer; len: Integer): Boolean;
asm
    push ebx           // Create ebx
    cmp EAX, EDX      // Str = Str2
    je @@true        // to exit true
    test eax, eax   // not Str
    je @@false     // to exit false
    test edx, edx // not Str2
    je @@false   // to exit false
    sub edx, eax              // Str2 := Str2 - Str;
    mov ebx, [eax]           // get Str 4 byte
    xor ebx, [eax + edx]    // Cmp Str2 4 byte
    jnz @@false            // Str <> Str2 to exit false
    sub ecx, 2            // dec 4
    { AnsiChar  : sub ecx, 4 }
    jbe @@true           // ecx <= 0 to exit true
    lea eax, [eax + 4]  // Next 4 byte
    @@To1:
    movdqa xmm0, DQWORD PTR [eax]       // Load Str 16 byte
    pcmpeqw xmm0, DQWORD PTR [eax+edx] // Load Str2 16 byte and cmp
    pmovmskb ebx, xmm0                // Mask cmp
    cmp ebx, 65535                   // Cmp mask
    jne @@Final                     // ebx <> 65535 to goto final
    add eax, 16                    // Next 16 byte
    sub ecx, 8                    // Skip 8 byte (16 wide)
    { AnsiChar  : sub ecx, 16 }
    ja @@To1                     // ecx > 0
    @@true:                       // Result true
    mov eax, 1                 // Set true
    pop ebx                   // Remove ebx
    ret                      // Return
    @@false:                  // Result false
    mov eax, 0             // Set false
    pop ebx               // Remove ebx
    ret                  // Return
    @@Final:
    cmp ecx, 7         // (ebx <> 65535) and (ecx > 7)
    { AnsiChar : cmp ecx, 15 }
    jae @@false       // to exit false
    movzx ecx, word ptr @@mask[ecx * 2 - 2] // ecx = mask[ecx]
    and ebx, ecx                           // ebx = ebx & ecx
    cmp ebx, ecx                          // ebx = ecx
    sete al                              // Equal / Set if Zero
    pop ebx                             // Remove ebx
    ret                                // Return
    @@mask: // array Mersenne numbers
    dw $000F, $003F, $00FF, $03FF, $0FFF, $3FFF
    { AnsiChar
    dw 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383
    }
end;

Semple 32bit https://vk.com/doc297044195_451679410

你調用了函數strcmp ,但你實際實現的是一個需要對齊的memcmp(const void *a, const void *b, size_t words) 如果指針不是16B對齊的話pcmpeqw xmm0, [mem] movdqapcmpeqw xmm0, [mem]都會pcmpeqw xmm0, [mem] (實際上,如果a+4不是16B對齊的,因為你執行前4個標量並增加4個字節。)

使用正確的啟動代碼和movdqu ,您可以處理任意對齊(達到要用作pcmpeqw的內存操作數的指針的對齊邊界)。 為方便起見,您可能要求兩個指針都以寬字符對齊開頭,但您不需要(特別是因為您只是返回true / false,而不是negative / 0 / positive作為排序順序。)


你問的是SSE2 pcmpeqwpcmpistrm性能,對吧? (像pcmpestrm這樣的顯式長度SSE4.2指令的吞吐量比隱式長度版本更差 ,因此當你不接近字符串的末尾時,請在主循環中使用隱式長度版本。請參閱Agner Fog的指令表和微觀指南)。

對於memcmp(或仔細實現的strcmp),使用SSE4.2可以做到的最好速度比在大多數CPU上使用SSE2(或SSSE3)所做的最好 也許對於非常短的字符串很有用,但對於memcmp的主循環卻沒有用。

在Nehalem上: pcmpistri是4 pcmpistri ,2c吞吐量(帶有內存操作數),因此沒有其他循環開銷,它可以跟上內存。 (Nehalem只有1個加載端口)。 pcmpestri吞吐量為6c:慢3倍。

在Sandybridge通過Skylake, pcmpistri xmm0, [eax]具有3c吞吐量,因此它太慢而無法跟上每個時鍾1個向量(2個加載端口)。 pcmpestri在大多數產品上都有4c的吞吐量,所以它沒有那么糟糕。 (可能對最后的部分向量有用,但在主循環中沒有用)。

在Silvermont / KNL上, pcmpistrm是最快的,並且每14個周期吞吐量運行一次,因此它對於簡單的東西來說是完全垃圾。

在AMD Jaguar上, pcmpistri是2c吞吐量,因此它實際上可以使用(只有一個加載端口)。 pcmpestri是5c吞吐量,所以很糟糕。

在AMD Ryzen上, pcmpistri也是2c吞吐量,所以它就是廢話。 (2個加載端口和每個時鍾5個uop前端吞吐量(如果有任何(或全部?)6個uop來自多uop指令)意味着你可以更快。

在AMD Bulldozer系列中, pcmpistri吞吐量為3c,直到Steamroller為5c。 pcmpestri吞吐量為10c。 它們被微編碼為7或27 m-ops,因此AMD沒有在它們上花費大量的硅。

在大多數CPU上,如果你正在充分利用它們只是pcmpeq / pmovmskb無法做到的東西,那么它們是值得的 但是,如果您可以使用AVX2或特別是AVX512BW,即使做更復雜的事情也可能會更快,更廣泛的矢量更多指令。 (沒有更寬版本的SSE4.2字符串指令。)也許SSE4.2字符串指令對於通常處理短字符串的函數仍然有用,因為寬向量循環通常需要更多的啟動/清理開銷。 此外,在一個不花費很多時間在SIMD循環中的程序中,在一個小功能中使用AVX或AVX512仍然會在下一毫秒左右降低最大turbo時鍾速度,並且很容易成為凈損失。


一個好的內部循環應該是負載吞吐量的瓶頸,或者盡可能接近。 movqdu / pcmpeqw [one-register] / pmovmskb / macro-fused-cmp + jcc只有4個融合域uops,所以這幾乎可以在Sandybridge家族CPU上實現


有關實現和一些基准測試,請參閱https://www.strchr.com/strcmp_and_strlen_using_sse_4.2 ,但這是針對C樣式的隱式長度字符串,您必須檢查0字節。 看起來你正在使用顯式長度字符串,因此在檢查長度相等后,它只是memcmp (或者我想如果您需要找到排序順序而不是等於/不等於,則必須將memcmp輸出到較短字符串的末尾。)

對於具有8位字符串的strcmp,在大多數CPU上,不使用SSE4.2字符串指令會更快。 有關某些基准測試(隱式長度字符串版本),請參閱strchr.com文章的評論。 例如,glibc不使用strcmp的SSE4.2字符串指令,因為它們在大多數CPU上都不會更快。 雖然他們可能是strstr的勝利。


glibc有幾個SSE2 / SSSE3 asm strcmpmemcmp實現 (它是LGPLed,因此你不能只將它復制到非GPL項目中,而是看看它們做了什么。)一些字符串函數(如strlen)每64字節只有一個分支,然后再回來整理緩存行中的哪個字節有命中。 但是他們的memcmp實現只是用movdqu / pcmpeqb 您可以使用pcmpeqw因為您想知道第一個不同的第一個16位元素的位置,而不是第一個字節。


您的SSE2實施可能更快。 您應該使用帶有movdqa的索引尋址模式,因為它不會與pcmpeqw微融合(在Intel Sandybridge / Ivybridge上;在Nehalem或Haswell +上很好),但是pcmpeqw xmm0, [eax]將保持微融合而不會發生分層。

您應該展開幾次以減少循環開銷。 您應該將指針增量與循環計數器結合使用,以便在更多CPU上使用cmp/jb而不是sub/ja :macro-fusion,並避免編寫寄存器(減少寄存器重命名所需的物理寄存器數量)。

您在英特爾Sandybridge / Ivybridge上的內循環將會運行

@@To1:
movdqa xmm0, DQWORD PTR [eax]       // 1 uop
pcmpeqw xmm0, DQWORD PTR [eax+edx] // 2 uops on Intel SnB/IvB, 1 on Nehalem and earlier or Haswell and later.
pmovmskb ebx, xmm0                // 1 uop
cmp ebx, 65535
jne @@Final                     // 1 uop  (macro-fused with cmp)
add eax, 16                    // 1 uop
sub ecx, 8
{ AnsiChar  : sub ecx, 16 }
ja @@To1                     // 1 uop (macro-fused with sub on SnB and later, otherwise 2)

這是7個融合域uops,因此它只能在主流Intel CPU的每次迭代中以最佳7/4周期從前端發出。 這遠遠不是每個時鍾2個負載的瓶頸。 在Haswell以及之后,它每次迭代是6/4個周期,因為索引尋址模式可以與像pcmpeqw這樣的2操作數加載修改指令保持微融合,但不是其他任何東西(如pabsw xmm0, [eax+edx] (不是' t讀取目的地)或AVX vpcmpeqw xmm0, xmm0, [eax+edx] (3個操作數))。 請參閱微融合和尋址模式


對於具有更好設置/清理的小字符串,這可能更有效。

在指針設置代碼中,如果首先檢查NULL指針,則可以保存cmp 你可以sub / jne減去檢查對於用相同的宏融合比較和支路等。 (它只會在英特爾Sandybridge系列上進行宏觀融合,只有Haswell可以在一個解碼模塊中進行2次宏觀融合。但Haswell / Broadwell / Skylake CPU很常見並且變得越來越普遍,這對其他沒有任何不利影響。 CPU,除非等指針是如此常見,以至於首先進行檢查很重要。)


在返回路徑中:始終使用xor eax,eax盡可能將寄存器歸零 ,而不是mov eax, 0

你似乎沒有避免從字符串末尾讀取。 您應該使用最終位於頁面末尾的字符串來測試您的函數,其中下一頁是未映射的。

對於早期的標量測試xor ebx, [eax + edx]優於cmp cmp/jnz可以與jcc進行宏融合,但xor不能。


您加載一個掩碼來處理清理,以覆蓋您讀取字符串末尾的情況。 你可能仍然可以使用通常的bsf來找到位圖中的第一個區別。 我想反過來not找不到比較相等的第一個位置,並檢查它是否小於剩余的字符串長度。

或者你可以用mov eax, -1shr生成掩碼,我想。 或者為了加載它,你有時可以使用滑動窗口進入...,0,0,0,-1,-1,-1,...數組,但你需要子字節偏移,這樣就不會工作。 (它適用於矢量蒙版,如果你想掩蓋和重做pmovmskb使用未對齊緩沖區進行矢量化:使用VMASKMOVPS:從未對齊計數生成掩碼?或者根本不使用該insn )。

你的方式也不錯,只要它沒有緩存錯過。 我可能會動態生成面具。 也許在另一個寄存器的循環之前 ,因為你可以屏蔽得到count % 8 ,所以掩碼生成可以與循環並行發生。

暫無
暫無

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

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