簡體   English   中英

我可以使用 SIMD 來加速字符串操作嗎?

[英]Can I use SIMD for speeding up string manipulation?

SIMD 指令是否僅用於矢量數值計算? 或者它是否適合一類字符串操作任務,例如將數據行寫入文本文件,其中行的順序無關緊要? 如果是這樣,我應該從哪些 API 或庫開始?

是的! 這實際上是在高性能解析庫中完成的。 一個例子: simdjson - 一種每秒可以解析千兆字節 JSON 的解析器。 自述文件中有一個關於 simdjson 的部分,其中有一個指向討論一些實現細節的演講的鏈接。

SIMD 指令對數值進行操作,但是一旦您處於該級別,“文本”就只是數值,例如 UTF-8 代碼點只是無符號 8 位整數,具有大量 SIMD 支持。 處理位圖充滿了對多個 8 位無符號整數的並行操作,而且很方便地發生這種情況,以至於 SIMD 指令集涵蓋了這些操作,因此它們中的很多也可用於文本處理。

I/O 比 CPU 慢很多數量級

並不真地。 它速度較慢,但​​是當 CPU 必須執行會影響流式傳輸性能的任務時,例如分支預測錯誤、緩存未命中或在死胡同上浪費大量推測執行資源時,CPU 很容易跟不上 I/O . 用於快速存儲訪問或多機通信的現代網卡會使 CPU 的內存端口飽和。 他們都。 並保持這種狀態。 但這是最先進的,目前相當昂貴(綁定 50 GBit 鏈接等)。 順序的,一次字節的解析器代碼比這慢得多。

是的,尤其是對於 ASCII,例如Convert a String In C++ To Upper Case 或檢查有效的 UTF-8 ( https://lemire.me/blog/2020/10/20/ridiculously-fast-unicode-utf-8-validation/ ),或檢查字符串是否恰好是UTF-8。 (如果是這樣,你知道你有固定寬度的字符,這對其他事情非常有用。)

正如 Daniel Lemire 報道的那樣,UTF-8 驗證的早期嘗試給出了“每個字符幾個 CPU 周期”。 但是使用 SIMD,他和合作者能夠實現每字節約 1 條指令,網絡速度為約 12GB/s。 (相比之下,Haswell 台式機的 DRAM 帶寬約為 25GB/s,或 Skylake 的 DDR4-2133 為 34GB/s)。


當然,大多數 C 庫已經有像strlenstrcpystrcasecmpstrstr等函數的手寫 asm 實現,如果它是一個勝利就使用 SIMD (比如在 x86-64 上,其中pmovmskb允許相對高效的比較/分支在任何/所有 SIMD 比較結果都是真還是假。)我回答的第一部分為什么 glibc 的 strlen 需要如此復雜才能快速運行? 有一些指向 glibc 在主流平台上實際使用的手動優化的 asm 的鏈接,而不是問題所詢問的可移植的純 C 后備。

https://github.com/WojciechMula/sse4-strstr有多種strstr實現。 子串搜索是一個更難的問題,有非平凡的算法選擇以及蠻力。 SSE4.2“字符串”指令可能對此有所幫助,但如果沒有,那么 SIMD 向量比較肯定可以用於更好的蠻力構建塊。

(像pcmpistri這樣的 SSE4.2“字符串”指令對於memcmp / strcmpstrlen肯定更糟,其中普通的 SSE2(或 AVX2)更好。參見SSE4.2 字符串指令比 SSE2 for memcmp 快多少?https://www .strchr.com/strcmp_and_strlen_using_sse_4.2 )

您甚至可以通過查找基於向量比較位圖的 shuffle 控制向量來做一些很酷的技巧,例如從字符串中獲取 IPv4 地址的最快方法如何使用 SIMD 實現 atoi? . 盡管我不確定 SIMD atoi 是否能勝過標量,尤其是對於短數字。


我天真地說 SIMD 無濟於事,因為對於長字符串內存帶寬將是瓶頸。 為什么不是這樣?

與現代 CPU 速度相比,DRAM帶寬確實相當不錯,尤其是當數據以字節塊而不是 8 字節double塊的形式出現時。 並且數據在復制后(例如來自read系統調用)在 L3 緩存中通常很熱。

即使數據必須來自 DRAM,現代台式機/筆記本電腦 CPU 也可以在每個內核時鍾周期加載大約 8 個字節,無論如何都在 2 倍之內,尤其是如果該內核不與其他內核上的其他帶寬密集型代碼競爭. 祝你好運跟上一個字節一次的標量循環。

此外,如果您只是執行read()系統調用來讓內核將一些數據從網絡緩沖區或頁面緩存中 memcpy 到您的進程的內存中,那么數據在 L3 緩存中可能仍然很熱,甚至在 L2 中。 Xeon CPU 甚至可以 DMA 進入 L3 緩存,或者類似的東西。 以內存帶寬為目標是一個相當低/沒有野心的目標,並且是一個不充分優化函數的糟糕借口,如果它實際上得到了很多使用。

處理相同數據的指令更少,讓亂序執行程序“看到”更遠的地方,並在硬件預取不會(例如跨頁面邊界)的情況下更早地開始對后面的頁面/緩存行進行按需加載。 並且還可以更好地將字符串處理與早期/后期的獨立工作重疊。

它也可以對超線程更加友好,如果在其上運行任何東西,HT 兄弟核心將具有更好的吞吐量。 (如果沒有很多線程處於活動狀態,則可能什么都沒有)。 此外,如果 SIMD 足夠高效,它可能會節省能源:通過流水線跟蹤指令是成本的很大一部分,而不是整數執行單元本身。 跑步時功率更高,但完成得更快,這很好:比賽睡覺。 與僅運行“廉價”指令相比,CPU 在完全空閑時可以節省更多電量。

SIMD 指令在非常低的級別上使用。 將數據寫入文本文件是一個更高的級別,涉及緩沖 I/O 等。

例如,您可以使用 SIMD 將字符串從小寫轉換為大寫。 將 SIMD 包裝到庫中是沒有實際意義的。 你自己寫指令。 這也意味着它們是特定於處理器的(例如 x86/AMD64 上的 SSE 變體)。

為了並行處理多行文本,您可以改用微並行化,例如 OpenMP 或 TBB 提供的微並行化。

但是,如果您堅持寫入文本文件的示例,我們將進入另一個性能優化領域(I/O 而不是計算)。

暫無
暫無

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

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