新網(wǎng)個(gè)人網(wǎng)站備案/關(guān)鍵詞查詢網(wǎng)
前言
朋友們有想過居然還有比memcpy更快的內(nèi)存拷貝嗎?
講道理,在這之前我沒想到過,我也一直覺得memcpy就是最快的內(nèi)存拷貝方法了。
也不知道老板最近是咋了,天天開會(huì)都強(qiáng)調(diào):“我們最近的目標(biāo)就一個(gè)字,性能優(yōu)化!”
一頓操作猛如虎,也沒提高5%。感覺自己實(shí)在是黔驢技窮,江郎才盡,想到又要被老板罵立馬滾蛋,心里就很不是滋味。
所謂車到山前必有路,船到橋頭自然直。嘿,有一天我剛好注意到我們的業(yè)務(wù)代碼里有大量的memcpy,正一籌莫展之時(shí),突然靈光一現(xiàn),腦海里閃過一個(gè)想法:memcpy還可以優(yōu)化嗎?
我想說,正是這個(gè)想法又讓我可以在老板面前暫時(shí)茍且偷生一段時(shí)間,實(shí)在是不得不佩服自己!
一、SIMD技術(shù)簡(jiǎn)介
這一小節(jié)介紹的內(nèi)容跟小節(jié)標(biāo)題很契合,就是介紹一下SIMD(Single Instruction Multiple Data,單指令多數(shù)據(jù))。啥意思呢,就是一條指令并發(fā)處理多條數(shù)據(jù)。形象一點(diǎn)講就是老板在桌上放了很多錢讓你拿,有同學(xué)喜歡一張一張的拿,還說我喜歡這種慢慢富有的感覺;SIMD就是,老子一把拿,我踏馬喜歡暴富!沒錯(cuò),它就是可以提升memcpy性能的關(guān)鍵核心技術(shù)。引用大佬畫的一張圖:
圖1
Scalar Operation就是指的SISD(Single Instruction Single Data,單指令單數(shù)據(jù)),這種方式完成上圖所有C[i]的計(jì)算需要串行執(zhí)行八次,因?yàn)槊總€(gè)時(shí)間點(diǎn),CPU的一條指令只能執(zhí)行一份數(shù)據(jù)。
SIMD,就是一次運(yùn)算就可以得到上述SISD的多次運(yùn)算結(jié)果,即一條指令可以并發(fā)執(zhí)行多份數(shù)據(jù),因此SIMD也稱為向量化計(jì)算。
到底是什么奇技淫巧使得SIMD具有并發(fā)執(zhí)行多份數(shù)據(jù)的能力呢?
其實(shí)就是CPU增加了專門用于向量化計(jì)算的向量寄存器,這些寄存器跟普通的寄存器不太一樣,它們的位寬都比較大,比如有128bit,256bit,甚至512bit,也就是說這些寄存器可以分別一次存儲(chǔ)16byte,32byte,64byte的數(shù)據(jù)。比如上圖的加法運(yùn)算,SISD一條指令只能完成一次兩個(gè)8byte數(shù)據(jù)的加法運(yùn)算。但是SIMD,一條指令就可以完成a[0:7] + b[0:7] = c[0:7],兩組數(shù)據(jù)的加法運(yùn)算。
CPU除了增加向量寄存器,還為向量寄存器配套了專門的指令集,比如Intel的MMX,SSE(MMX的升級(jí)版),AVX(SSE的升級(jí)版)指令集。CPU運(yùn)算時(shí),識(shí)別到指令集命令,就會(huì)采用指令集對(duì)應(yīng)的SIMD計(jì)算方法完成并發(fā)運(yùn)算。Intel指令集查詢鏈接:個(gè)人學(xué)習(xí)和技術(shù)驗(yàn)證
二、memcpy_fast方法
帶著memcpy是否還可以繼續(xù)優(yōu)化的疑問,一通搜索,真找到了采用SIMD技術(shù)的memcpy方法:memcpy_fast,鏈接:GitHub - skywind3000/FastMemcpy: Speed-up over 50% in average vs traditional memcpy in gcc 4.9 or vc2012
分析了一下源碼實(shí)現(xiàn)。
(1)SSE指令集實(shí)現(xiàn)的fast拷貝
1、使用_mm_loadu_si128指令,從src + 0的位置取走128bit,即16字節(jié),然后依次類推,src + 1,...,直至src + 7,一共取走16byte * 8=128byte,取出的內(nèi)容分別儲(chǔ)存到向量寄存器c0,c1,...,c7;
2、使用_mm_prefetch實(shí)現(xiàn)數(shù)據(jù)預(yù)取,提前把數(shù)據(jù)從內(nèi)存加載到cache,保證CPU對(duì)數(shù)據(jù)的快速讀取;
3、使用_mm_store_si128指令,將c0,c1,...,c7寄存器的內(nèi)容分別存儲(chǔ)至目的地址dst?+ 0,?dst + 1,...,?dst + 7的八個(gè)位置。
利用指令集、向量寄存器、數(shù)據(jù)預(yù)取技術(shù)實(shí)現(xiàn)了每次16byte的并發(fā),128byte的批次拷貝。
圖2
(2)AVX指令集實(shí)現(xiàn)的fast拷貝
與SSE指令集實(shí)現(xiàn)內(nèi)存拷貝邏輯一致。
1、由AVX指令集的_mm256_loadu_si256,實(shí)現(xiàn)每次256byte的數(shù)據(jù)加載;
2、由AVX指令集的_mm256_storeu_si256,實(shí)現(xiàn)每次256byte數(shù)據(jù)的存儲(chǔ)。
可以預(yù)料,當(dāng)然是寄存器位寬越大,性能會(huì)越好,也就是從理論上說使用AVX指令集會(huì)比SSE指令集更快。
圖3
?三、memcpy?VS?memcpy_fast
我們一起來看看memcpy與使用了SIMD技術(shù)的memcpy_fast的性能對(duì)比吧。
直接將memcpy_fast源碼下載后編譯即可,鏈接:GitHub - skywind3000/FastMemcpy: Speed-up over 50% in average vs traditional memcpy in gcc 4.9 or vc2012
SSE指令集編譯命令:gcc -O3 -msse2 FastMemcpy.c -o FastMemcpy
AVX指令集編譯命令:gcc -O3 -mavx FastMemcpy_Avx.c -o FastMemcpy_Avx
(1)SSE指令集下性能結(jié)果對(duì)比
綠色框里,即內(nèi)存拷貝在1MB以下時(shí),特別是拷貝長度在(1024?~?1048576)bytes時(shí),拷貝性能有顯著提升。但是靠拷貝長度超過1MB時(shí),memcpy_fast居然比memcpy更慢了,發(fā)生了什么?
圖4
繼續(xù)查閱源碼,發(fā)現(xiàn)在大于2MB時(shí),與2MB長度以下的拷貝相比,采用了不同的SIMD拷貝指令。即在拷貝長度小于等于 cachesize = 0x200000 時(shí),使用?_mm_store_si128進(jìn)行數(shù)據(jù)存儲(chǔ);在大于0x200000 時(shí),使用_mm_stream_si128進(jìn)行數(shù)據(jù)存儲(chǔ)。
圖5
我把大長度數(shù)據(jù)拷貝由_mm_stream_si128替換為中等長度數(shù)據(jù)拷貝指令_mm_store_si128后,memcpy_fast無論是中等長度,還是大長度的數(shù)據(jù)拷貝性能都比memcpy要好。
圖6
(2)AVX指令集下性能結(jié)果對(duì)比?
同樣,將AVX大長度數(shù)據(jù)拷貝也進(jìn)行優(yōu)化,將指令_mm256_stream_si256替換為_mm256_storeu_si256,AVX指令集的性能測(cè)試結(jié)果如下圖7所示。
簡(jiǎn)單總結(jié)為兩點(diǎn):
1、圖6和圖7進(jìn)行了充分說明,相同長度的數(shù)據(jù)拷貝,AVX確實(shí)比SSE性能更高;
2、拷貝長度在(512 ~ 8388608)bytes,memcpy_fast都比memcpy要提升一倍不止,有的長度,內(nèi)存拷貝性能甚至提升了4倍!
圖7
四、結(jié)語
? 這種內(nèi)存拷貝的性能提升,有什么好處呢?
想到一個(gè)場(chǎng)景,比如生產(chǎn)環(huán)境的網(wǎng)關(guān)設(shè)備(FW,VPN等等),內(nèi)存拷貝的性能提升可以降低網(wǎng)關(guān)設(shè)備的流量處理時(shí)延,提升網(wǎng)絡(luò)質(zhì)量,從而進(jìn)一步提高用戶使用體驗(yàn)。
?