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