比memcpy還要快的記憶體複製,老哥瞭解一下?

BARBARIANS發表於2023-04-03

本文來自部落格園,作者:T-BARBARIANS,轉載請註明原文連結:https://www.cnblogs.com/t-bar/p/17262147.html 謝謝!

 

前言

  朋友們有想過居然還有比memcpy更快的記憶體複製嗎?

  講道理,在這之前我沒想到過,我也一直覺得memcpy就是最快的記憶體複製方法了。

  也不知道老闆最近是咋了,天天開會都強調:“我們最近的目標就一個字:效能最佳化!”

  一頓操作猛如虎,也沒提高5%。感覺自己實在是黔驢技窮,江郎才盡,想到又要被老闆罵立馬滾蛋,心裡就很不是滋味。

  所謂車到山前必有路,船到橋頭自然直。嘿,有一天我剛好注意到我們的業務程式碼裡有大量的memcpy,正一籌莫展之時,突然靈光一現,腦海裡閃過一個想法:memcpy還可以最佳化嗎?

  我想說,正是這個想法又讓我可以在老闆面前暫時苟且偷生一段時間,實在是不得不佩服自己!

一、SIMD技術簡介

  這一小節介紹的內容跟小節標題很契合,就是介紹一下SIMD(Single Instruction Multiple Data,單指令多資料),啥意思呢,就是一條指令併發處理多條資料。形象一點講就是老闆在桌上放了很多錢讓你拿,有同學喜歡一張一張的拿,還說我喜歡這種慢慢富有的感覺;SIMD就是,老子一把拿,我踏馬喜歡暴富!沒錯,它就是可以提升memcpy效能的關鍵核心技術。引用大佬畫的一張圖:

 圖1

  Scalar Operation就是指的SISD(Single Instruction Single Data,單指令單資料),這種方式完成上圖所有C[i]的計算需要序列執行八次,因為每個時間點,CPU的一條指令只能執行一份資料。

  SIMD,就是一次運算就可以得到上述SISD的多次運算結果,即一條指令可以併發執行多份資料,因此SIMD也稱為向量化計算。

  到底是什麼奇技淫巧使得SIMD具有併發執行多份資料的能力呢?

  其實就是CPU增加了專門用於向量化計算的向量暫存器,這些暫存器跟普通的暫存器不太一樣,它們的位寬都比較大,比如有128bit,256bit,甚至512bit,也就是說這些暫存器可以分別一次儲存16byte,32byte,64byte的資料。比如上圖的加法運算,SISD一條指令只能完成一次兩個8byte資料的加法運算。但是SIMD,一條指令就可以完成a[0:7] + b[0:7] = c[0:7],兩組資料的加法運算。

  CPU除了增加向量暫存器,還為向量暫存器配套了專門的指令集,比如Intel的MMX,SSE(MMX的升級版),AVX(SSE的升級版)指令集。CPU運算時,識別到指令集命令,就會採用指令集對應的SIMD計算方法完成併發運算。Intel指令集查詢連結:http://kntan.top/#!=undefined

二、memcpy_fast方法

  帶著memcpy是否還可以繼續最佳化的疑問,一通搜尋,真找到了採用SIMD技術的memcpy方法:memcpy_fast,連結:https://github.com/skywind3000/FastMemcpy

  分析了一下原始碼實現

  (1)SSE指令集實現的fast複製

  1、使用_mm_loadu_si128指令,從src + 0的位置取走128bit,即16位元組,然後依次類推,src + 1,...,直至src + 7,一共取走16byte * 8=128byte,取出的內容分別儲存到向量暫存器c0,c1,...,c7;

  2、使用_mm_prefetch實現資料預取,提前把資料從記憶體載入到cache,保證CPU對資料的快速讀取;

  3、使用_mm_store_si128指令,將c0,c1,...,c7暫存器的內容分別儲存至目的地址dst + 0, dst + 1,..., dst + 7的八個位置。

  利用指令集、向量暫存器、資料預取技術實現了每次16byte的併發,128byte的批次複製。

圖2

  (2)AVX指令集實現的fast複製

  與SSE指令集實現記憶體複製邏輯一致。

  1、由AVX指令集的_mm256_loadu_si256,實現每次256byte的資料載入;

  2、由AVX指令集的_mm256_storeu_si256,實現每次256byte資料的儲存。

  可以預料,當然是暫存器位寬越大,效能會越好,也就是從理論上說使用AVX指令集會比SSE指令集更快。

圖3

 三、memcpy VS memcpy_fast

  我們一起來看看memcpy與使用了SIMD技術的memcpy_fast的效能對比吧。

  直接將memcpy_fast原始碼下載後編譯即可,連結:https://github.com/skywind3000/FastMemcpy

  SSE指令集編譯命令:gcc -O3 -msse2 FastMemcpy.c -o FastMemcpy

  AVX指令集編譯命令:gcc -O3 -mavx FastMemcpy_Avx.c -o FastMemcpy_Avx

  (1)SSE指令集下效能結果對比 

  綠色框裡,即記憶體複製在1MB以下時,特別是複製長度在(1024 ~ 1048576)bytes時,複製效能有顯著提升。但是靠複製長度超過1MB時,memcpy_fast居然比memcpy更慢了,發生了什麼?

圖4

  繼續查閱原始碼,發現在大於2MB時,與2MB長度以下的複製相比,採用了不同的SIMD複製指令。即在複製長度小於等於 cachesize = 0x200000 時,使用 _mm_store_si128進行資料儲存;在大於0x200000 時,使用_mm_stream_si128進行資料儲存。

圖5

  我把大長度資料複製由_mm_stream_si128替換為中等長度資料複製指令_mm_store_si128後,memcpy_fast無論是中等長度,還是大長度的資料複製效能都比memcpy要好。

圖6
  (2)AVX指令集下效能結果對比 

  同樣,將AVX大長度資料複製也進行最佳化,將指令_mm256_stream_si256替換為_mm256_storeu_si256,AVX指令集的效能測試結果如下圖7所示。

  簡單總結為兩點:

  1、圖6和圖7進行了充分說明,相同長度的資料複製,AVX確實比SSE效能更高;

  2、複製長度在(512 ~ 8388608)bytes,memcpy_fast都比memcpy要提升一倍不止,有的長度,記憶體複製效能甚至提升了4倍!

圖7

四、結語

   這種記憶體複製的效能提升,有什麼好處呢?

  想到一個場景,比如生產環境的閘道器裝置(FW,VPN等等),記憶體複製的效能提升可以降低閘道器裝置的流量處理時延,提升網路質量,從而進一步提高使用者使用體驗。

  

  把這份最佳化思路給老闆做了彙報,老闆揚起嘴角笑了笑並說道:“對你來說,餅可能不香了!”

  技術是不斷實踐積累的,在此分享出來與大家一起共勉!

  如果文章對你有些許幫助,還請各位技術愛好者登入點贊呀,非常感謝!

 

  本文來自部落格園,作者:T-BARBARIANS,轉載請註明原文連結:https://www.cnblogs.com/t-bar/p/17262147.html 謝謝!

相關文章