在Cortex-A8平臺(tái)下memcpy ARM/NEON匯編性能的測(cè)試
前言
在C run time library中,memcpy是重要的函數(shù),對(duì)應(yīng)用軟件的性能有著重要的影響。ARM芯片發(fā)展到Cortex-A8[1][2]架構(gòu),不但頻率有了很大提升,而且架構(gòu)設(shè)計(jì)有了很大地改進(jìn)。其中增加的NEON指令,是類似于原先X86平臺(tái)下的MMX指令,是為多媒體而設(shè)計(jì)。但因?yàn)檫@類指令一次可以處理64-bit數(shù)據(jù),對(duì)memcpy函數(shù)性能提升也很有幫助。本文主要是測(cè)試采用NEON[2]指令的多種memcpy實(shí)現(xiàn),探討NEON指令和預(yù)取(preload)指令對(duì)性能的影響,以及在芯片優(yōu)化和工藝進(jìn)步后,這些影響的變化趨勢(shì)。同時(shí)希望芯片設(shè)計(jì)人員在了解軟件實(shí)現(xiàn)的基礎(chǔ)上,給予一個(gè)知其然,也知其所以然的解釋,進(jìn)而指導(dǎo)進(jìn)一步提高性能的方向。
本文引用地址:http://cafeforensic.com/article/201611/317414.htm平臺(tái)介紹
本次的測(cè)試平臺(tái)來(lái)源于筆者工作項(xiàng)目中接觸到的Cortex-A8平臺(tái)。見(jiàn)下面列表:
- FreeScale i.MX51 / i.MX53
- QualComm msm8x50 / msm7x30
- Samsung s5pc100 / s5pc110
- TI omap 3430 / omap 3730
i.MX5 family
i.MX5 family的介紹見(jiàn)[6][7]。其中i.MX535可以運(yùn)行在800MHZ / 1000MHZ兩種頻率上。
- i.MX515
- freq: 800MHZ
- cache size: 32KB/32KB I/D Cache and 256KB L2 Cache
- cache line: 64-bit wide(NEON), 64-byte / line
- i.MX535
- freq: 800MHZ / 1000MHZ
- cache size: 32KB/32KB I/D Cache and 256KB L2 Cache
- cache line: 64-bit wide(NEON), 64-byte / line
Snapdragon family
Snapdragon的介紹見(jiàn)[8][9][10]。其中msm7x30可以運(yùn)行在800MHZ / 1000MHZ兩種頻率上。此外Snapdragon cache特別之處是128-bit wide(NEON), 128-byte / line。標(biāo)準(zhǔn)Cortex-A8中,該數(shù)值為64-bit wide(NEON), 64-byte / line。這對(duì)性能有較大影響。
- msm8x50
- freq: 1000MHZ
- cache size: 32KB/32KB I/D Cache and 256KB L2 Cache
- cache line: 128-bit wide(NEON), 128-byte / line
- msm7x30
- freq: 800MHZ / 1000MHZ
- cache size: 32KB/32KB I/D Cache and 256KB L2 Cache
- cache line: 128-bit wide(NEON), 128-byte / line
s5pc family
s5pc family參考平臺(tái)見(jiàn)[11]。
- s5pc100
- freq: 665MHZ
- cache size: 32KB/32KB I/D Cache and 256KB L2 Cache
- cache line: 64-bit wide(NEON), 64-byte / line
- s5pc110
- freq: 1000MHZ
- cache size: 32KB/32KB I/D Cache and 512KB L2 Cache
- cache line: 64-bit wide(NEON), 64-byte / line
omap3 family
omap3 family參考平臺(tái)見(jiàn)[12][13][14]。
- omap3430
- freq: 550MHZ
- cache size: 16KB/16KB I/D Cache and 256KB L2 Cache
- cache line: 64-bit wide(NEON), 64-byte / line
- omap3730
- freq: 1000MHZ
- cache size: 32KB/32KB I/D Cache and 256KB L2 Cache
- cache line: 64-bit wide(NEON), 64-byte / line
memcpy實(shí)現(xiàn)介紹
memcpy的實(shí)現(xiàn)在ARM平臺(tái)上的發(fā)展有3類版本:
- C語(yǔ)言版本
- ARM匯編版本
- NEON匯編版本
ARM公司的文檔[4]對(duì)memcpy的實(shí)現(xiàn)有很好描述。有人[5][19][20]還進(jìn)一步闡述了實(shí)現(xiàn)原理和技巧。簡(jiǎn)述如下:
- NEON指令一次可以處理64-bit數(shù)據(jù),效率更高。
- NEON架構(gòu)與L1/L2 cache都有直連,在OS層級(jí)enable后,可以獲得更好的性能。
- ARM / NEON的pipeline有可能異步處理,交替使用ARM / NEON指令有可能獲得更好的性能。
- 在一次循環(huán)中,用盡可能多的寄存器copy更多的數(shù)據(jù),保證pipeline有更好的效率。目前一次最大處理塊為128-byte。
- 對(duì)cache的操作有講究。
- memcpy屬于一次掃瞄無(wú)回溯的操作,對(duì)于cache采用預(yù)取(preload)策略可以提高h(yuǎn)it rate。所以匯編版本中一定會(huì)使用pld指令提示ARM預(yù)先把cache line填充好。
- pld指令中的offset很有講究。一般為64-byte的倍數(shù)。在ARMv5TE平臺(tái)是一個(gè)循環(huán)用一個(gè)pld指令。在Cortex-A8平臺(tái)上速度更快,需要一個(gè)循環(huán)用2~3個(gè)pld指令填充cache line。這樣一個(gè)循環(huán)消費(fèi)2~3個(gè)時(shí)鐘周期換得cache hit rate提高,效果是值得的。
- 進(jìn)一步的,Cortex-A8架構(gòu)提供了preload engine指令,可以讓軟件更深地影響cache,以便讓cache hit rate得到提高。不過(guò)要在用戶空間使用ple指令,需要在OS中打補(bǔ)丁開(kāi)放權(quán)限。
C語(yǔ)言版本
C語(yǔ)言版本主要是做對(duì)比。采用兩個(gè)實(shí)現(xiàn):
- 32-bit wide copy。后面標(biāo)記為in32_cpy。
- 16-byte wide copy。后面標(biāo)記為vec_cpy。這個(gè)實(shí)現(xiàn)的技巧是采用gcc的向量擴(kuò)展"__attribute__ ((vector_size(16)))",在C語(yǔ)言層級(jí)實(shí)現(xiàn)16-byte wide copy,將具體實(shí)現(xiàn)交給編譯器。
值得注意的事情是,編譯器不會(huì)主動(dòng)插入pld指令。因?yàn)榫幾g器無(wú)法判斷應(yīng)用對(duì)內(nèi)存的訪問(wèn)模式。
ARM匯編版本
ARM匯編版本也主要是做對(duì)比。采用兩個(gè)實(shí)現(xiàn):
- Siarhei Siamashka實(shí)現(xiàn)[15]。后面標(biāo)記為arm9_memcpy。他是為Nokia N770做的優(yōu)化。
- Nicolas Pitre實(shí)現(xiàn)[16]。后面標(biāo)記為armv5te_memcpy。這是目前glibc里面缺省的arm memcpy實(shí)現(xiàn)。
NEON匯編版本
NEON匯編版本采用四個(gè)實(shí)現(xiàn):
- M?ns Rullg?rd實(shí)現(xiàn)[19]。這是一個(gè)128-byte-align block的最簡(jiǎn)單的實(shí)現(xiàn)。沒(méi)有判斷不是128-byte align的情況。因此不是實(shí)用的版本。但通過(guò)這類實(shí)現(xiàn),可以考察memcpy性能的極限。他總共提供4種實(shí)現(xiàn)。
- 全ARM匯編的實(shí)現(xiàn)。后面標(biāo)記為memcpy_arm。此外,筆者還將其中的pld指令去掉,做為對(duì)比試驗(yàn),考察pld指令的影響。后面標(biāo)記為memcpy_arm_nopld。
- 全NEON匯編的實(shí)現(xiàn)。后面標(biāo)記為memcpy_neon。此外,筆者還將其中的pld指令去掉,做為對(duì)比試驗(yàn),考察pld指令的影響。后面標(biāo)記為memcpy_neon_nopld。
- ARM / NEON指令交替使用的實(shí)現(xiàn)。后面標(biāo)記為memcpy_armneon。此外,筆者還將其中的pld指令去掉,做為對(duì)比試驗(yàn),考察pld指令的影響。后面標(biāo)記為memcpy_armneon_nopld。
- ple + NEON的實(shí)現(xiàn)。后面標(biāo)記為memcpy_ple_neon。此外,筆者還將其中的NEON指令換成ARM指令,做為對(duì)比試驗(yàn),考察ple指令對(duì)ARM/NEON指令的影響。后面標(biāo)記為memcpy_ple_arm。因?yàn)檫@個(gè)實(shí)現(xiàn)需要對(duì)linux kernel打補(bǔ)丁,在omap3430平臺(tái)上沒(méi)有成功。在Snapdragon平臺(tái)上更換kernel有些麻煩,所以也沒(méi)有測(cè)試。
- CodeSourcery實(shí)現(xiàn)[17]。這是CodeSourcery toolchain中的glibc里面的實(shí)現(xiàn)。也分兩種實(shí)現(xiàn)。
- ARM實(shí)現(xiàn)。后面標(biāo)記為memcpy_arm_codesourcery。筆者還將其中的pld指令去掉,做為對(duì)比試驗(yàn),考察pld指令的影響。后面標(biāo)記為memcpy_arm_codesourcery_nopld。
- NEON實(shí)現(xiàn)。后面標(biāo)記為memcpy_neon_codesourcery。這也是Android bionic里面采用的NEON實(shí)現(xiàn)。筆者還將其中的pld指令去掉,做為對(duì)比試驗(yàn),考察pld指令的影響。后面標(biāo)記為memcpy_neon_codesourcery_nopld。
- QualComm實(shí)現(xiàn)[18]。后面標(biāo)記為memcpy_neon_qualcomm。這是QualComm在Code Aurora Forum中為Snapdragon平臺(tái)開(kāi)發(fā)的優(yōu)化版本。主要是對(duì)8660/8650A平臺(tái)的優(yōu)化。這個(gè)版本的特點(diǎn)是針對(duì)L2 cache line size=128bytes而設(shè)計(jì),pld offset設(shè)置得特別大。結(jié)果在其它Cortex-A8平臺(tái)上沒(méi)有效果。所以筆者將pld offset改為M?ns Rullg?rd實(shí)現(xiàn)的數(shù)值。筆者還將其中的pld指令去掉,做為對(duì)比試驗(yàn),考察pld指令的影響。后面標(biāo)記為memcpy_neon_qualcomm_nopld。
- Siarhei Siamashka實(shí)現(xiàn)[20]。后面標(biāo)記為memcpy_neon_siarhei。這是Siarhei Siamashka向glibc提交的NEON版本,沒(méi)有被glibc采納。但是在MAEMO項(xiàng)目中得到采用。這個(gè)版本的特點(diǎn)是pld offset是從小到大增長(zhǎng)的,以期望適應(yīng)block size的變化。
測(cè)試方案介紹
測(cè)試方案十分簡(jiǎn)單。參考了movial memory tester的實(shí)現(xiàn)[21]。執(zhí)行步驟如下:
- 先對(duì)每個(gè)實(shí)現(xiàn)進(jìn)行正確性的驗(yàn)證。主要方法是以隨機(jī)的block size & offset,填充隨機(jī)的內(nèi)容,然后執(zhí)行memcpy操作,然后再用系統(tǒng)的memcmp函數(shù)對(duì)兩塊內(nèi)存做校驗(yàn)。
- 然后對(duì)每個(gè)實(shí)現(xiàn)以不同的block size調(diào)用400次。如果total copy size < 1MB,則增加count直到滿足要求。對(duì)總操作計(jì)時(shí)。
- 以total copy size / total copy time公式計(jì)算memcpy bandwidth。
上述提到的block size = 2^n ( 7 <= n <= 23 )。
此外,這個(gè)測(cè)試程序運(yùn)行在openembedded-gpe軟件系統(tǒng)中。QualComm / Samsung硬件平臺(tái)只提供Android軟件系統(tǒng),要更換到GPE系統(tǒng)有些麻煩,則采用chroot方式進(jìn)行測(cè)試。不論是哪種軟件平臺(tái),都是進(jìn)入到圖形系統(tǒng)后,靜置,等待黑屏,然后再進(jìn)行測(cè)試。
下表是運(yùn)行環(huán)境的統(tǒng)計(jì)。
硬件平臺(tái)
軟件環(huán)境
imx51 800MHZ
openembedded-gpe
imx53 1000MHZ
openembedded-gpe
imx53 800MHZ
openembedded-gpe
msm7230 1000MHZ
Android + chroot
msm7230 800MHZ
Android + chroot
msm8250 1000MHZ
Android + chroot
omap3430 550MHZ
openembedded-gpe
omap3730 1000MHZ
openembedded-gpe
s5pc100 665MHZ
Android + chroot
s5pc110 1000MHZ
Android + chroot
下表是測(cè)試項(xiàng)目的統(tǒng)計(jì)。
實(shí)現(xiàn)方案
i.MX51
i.MX53
Snapdragon
s5pc1xx
omap3430
omap3730
int32_cpy
YES
YES
YES
YES
YES
YES
vec_cpy
YES
YES
YES
YES
YES
YES
arm9_memcpy
YES
YES
YES
YES
YES
YES
armv5te_memcpy
YES
YES
YES
YES
YES
YES
memcpy_arm
YES
YES
YES
YES
YES
YES
memcpy_arm_nopld
YES
NO
YES
YES
YES
YES
memcpy_neon
YES
YES
YES
YES
YES
YES
memcpy_neon_nopld
YES
NO
YES
YES
YES
YES
memcpy_armneon
YES
YES
YES
YES
YES
YES
memcpy_ple_arm
YES
YES
N/A
YES
N/A
YES
memcpy_ple_neon
YES
YES
N/A
YES
N/A
YES
memcpy_arm_codesourcery
YES
YES
YES
YES
YES
YES
memcpy_arm_codesourcery_nopld
YES
NO
YES
YES
YES
YES
memcpy_neon_codesourcery
YES
YES
YES
YES
YES
YES
memcpy_neon_codesourcery_nopld
YES
NO
YES
YES
YES
YES
memcpy_neon_qualcomm
YES
YES
YES
YES
YES
YES
memcpy_neon_qualcomm_nopld
YES
NO
YES
YES
YES
YES
memcpy_neon_siarhei
YES
YES
YES
YES
YES
YES
注1:因?yàn)閕.MX53 EVK板子發(fā)生故障,未能測(cè)試所有no pld的測(cè)試項(xiàng)。
注2:在給omap3430打開(kāi)preload engine后,測(cè)試產(chǎn)生非法指令錯(cuò),未能測(cè)試ple的測(cè)試項(xiàng)。
注3:要替換Snapdragon kernel有些麻煩,未能測(cè)試ple的測(cè)試項(xiàng)。
測(cè)試結(jié)果與分析
下面的圖表限于頁(yè)面大小不能很好地顯示細(xì)節(jié)。具體的數(shù)據(jù)和大圖可到數(shù)據(jù)表文檔中查看。
各個(gè)硬件平臺(tái)上各種實(shí)現(xiàn)的表現(xiàn)
imx51 800MHZ
imx53 1000MHZ
imx53 800MHZ
msm7230 1000MHZ
msm7230 800MHZ
msm8250 1000MHZ
omap3430 550MHZ
omap3730 1000MHZ
s5pc100 665MHZ
s5pc110 1000MHZ
小結(jié)
- 在block size = 512B ~ 32K之間,有一個(gè)性能高臺(tái),block size = 256K也有一個(gè)性能的轉(zhuǎn)折。
- 這個(gè)特性體現(xiàn)了32KB L1 / 256KB L2 cache的影響。
- 小于512B的性能不佳,可能與函數(shù)調(diào)用,函數(shù)開(kāi)始的塊對(duì)齊技巧造成的損耗有關(guān),也可能與block size太小,cache沒(méi)有準(zhǔn)備好函數(shù)就結(jié)束了有關(guān)。
- 文檔[]對(duì)memcpy的實(shí)現(xiàn)還是有指導(dǎo)意義的。但隨著芯片內(nèi)部的優(yōu)化和工藝的提升,有些規(guī)則發(fā)生了變化。
- NEON指令的性能總是要高于ARM指令的性能。但交替使用ARM/NEON指令并不總是帶來(lái)性能的提升。隨著發(fā)展ARM/NEON指令之間性能差在縮小。
- pld指令的作用越來(lái)越小。在較老的芯片上,如omap3430,采用pld指令后,同一個(gè)實(shí)現(xiàn)可以有50%的性能提升。在較新的芯片上,如msm7230/s5pc110上,性能基本沒(méi)有區(qū)別,甚至同一個(gè)實(shí)現(xiàn)沒(méi)有pld指令后,性能稍稍有些提升。這也許是因?yàn)閜ld指令沒(méi)有效果,倒反在每個(gè)循環(huán)中浪費(fèi)了時(shí)鐘周期造成的。
- 采用ple指令的實(shí)現(xiàn)的性能令人大失所望。這也說(shuō)明如果沒(méi)有很好的模型設(shè)計(jì),軟件去干預(yù)cache的使用,很容易會(huì)造成性能的惡化。
- Snapdragon平臺(tái)有最好的cache性能。超出cache后,各種實(shí)現(xiàn)(包括C語(yǔ)言實(shí)現(xiàn))的性能基本一致,也很高效。這也許是Snapdragon平臺(tái)13-stage load/store pipeline[][]的設(shè)計(jì)造成的。這個(gè)特性對(duì)高級(jí)語(yǔ)言是有好處的。因?yàn)榫幊滩豢赡茉诤芏嗟胤讲捎脜R編語(yǔ)言。這樣開(kāi)發(fā)人員就不必過(guò)多地考慮匯編優(yōu)化,依賴編譯器就可以了。
- s5pc110平臺(tái)有最好的平均性能。超出cache后,NEON實(shí)現(xiàn)的性能最好,基本保持一條水平線。
在small/big block size下各個(gè)硬件平臺(tái)的表現(xiàn)
性能因?yàn)閎lock size分為fit in cache / out of cache兩種表現(xiàn),所以做兩個(gè)剖面做對(duì)比分析。
- 8K block size。體現(xiàn)fit in cache時(shí)的性能。
- 8M block size。體現(xiàn)out of cache時(shí)的性能。
M?ns Rullg?rd的實(shí)現(xiàn)
因?yàn)镸?ns Rullg?rd的實(shí)現(xiàn)最簡(jiǎn)單,除了一個(gè)循環(huán)體外,沒(méi)有其它判斷代碼,可以認(rèn)為是體現(xiàn)平臺(tái)速度極限的實(shí)現(xiàn)。
ARM的實(shí)現(xiàn)
NEON的實(shí)現(xiàn)
小結(jié)
- NEON指令的性能總是要高于ARM指令的性能。隨著發(fā)展ARM/NEON指令之間性能差在縮小。
- 交替使用ARM/NEON指令,在fit in cache條件下性能要差于NEON版本。在out of cache條件下,兩個(gè)版本性能基本一樣。
- 在fit in cache條件下,Snapdragon平臺(tái)有最好的性能。超過(guò)第二名s5pc110大約為43%。
- 在out of cache條件下,s5pc110有最好的性能。超過(guò)第二名omap3730大約為57%。
- 在同一個(gè)硬件平臺(tái)下,超頻(如i.MX53 800/1000MHZ & msm7x30 800/1000MHZ)對(duì)memory性能影響很小。
實(shí)用ARM/NEON實(shí)現(xiàn)在各個(gè)硬件平臺(tái)的表現(xiàn)
通過(guò)同一種實(shí)現(xiàn)在不同硬件平臺(tái)上性能的對(duì)比,結(jié)合上一節(jié)的圖表,可以評(píng)價(jià)一種實(shí)現(xiàn)的平均性能,也就是適應(yīng)性。
ARM的實(shí)現(xiàn)
NEON的實(shí)現(xiàn)
小結(jié)
- 同一種的實(shí)現(xiàn),在不同的硬件平臺(tái)上都有不同的表現(xiàn)。沒(méi)有一種實(shí)現(xiàn)在所有平臺(tái)上是最好的。
- Codesourcery版本,包括ARM/NEON版本,有很好的適應(yīng)性。不愧是做toolchain的公司。
- Siarhei Siamashka的NEON版本也有很好的適應(yīng)性。NOKIA的技術(shù)實(shí)力也很強(qiáng)。這哥們好像也是pixman項(xiàng)目里面做NEON優(yōu)化的主力。
- Qualcomm版本只適合Snapdragon平臺(tái)。期待以后能在msm8660以及后續(xù)的芯片上進(jìn)行測(cè)試。
總結(jié)
- 在block size = 512B ~ 32K之間,有一個(gè)性能高臺(tái),block size = 256K也有一個(gè)性能的轉(zhuǎn)折。這個(gè)特性體現(xiàn)了32KB L1 / 256KB L2 cache的影響。
- NEON指令的性能總是要高于ARM指令的性能。隨著發(fā)展ARM/NEON指令之間性能差在縮小。交替使用ARM/NEON指令,性能往往要差于NEON版本。
- 如果沒(méi)有很好的模型設(shè)計(jì),軟件去干預(yù)cache的使用,很容易會(huì)造成性能的惡化。
- 在fit in cache條件下,Snapdragon平臺(tái)有最好的性能。
- 在out of cache條件下,s5pc110有最好的性能。
- 在同一個(gè)硬件平臺(tái)下,超頻對(duì)memory性能影響很小。
- 同一種的實(shí)現(xiàn),在不同的硬件平臺(tái)上都有不同的表現(xiàn)。沒(méi)有一種實(shí)現(xiàn)在所有平臺(tái)上是最好的。
進(jìn)一步的測(cè)試
因?yàn)樵贑ortex-A8系列芯片里,NEON模塊是必有的。而在Cortex-A9系列芯片里,NEON模塊是可選的。因?yàn)镹EON模塊會(huì)影響到die size,因而影響功耗和成本。因此有些Cortex-A9芯片,如Nvidia Tegra250,沒(méi)帶有NEON模塊。那么有無(wú)NEON模塊會(huì)對(duì)軟件性能造成什么樣的影響呢?
評(píng)論