ARM和NEON指令

http://blog.csdn.net/chshplp_liaoping/article/details/12752749

在移動平臺上進行一些複雜算法的開發,一般需要用到指令集來進行加速。目前在移動上使用最多的是ARM芯片。

ARM是微處理器行業的一家知名企業,其芯片結構有:armv5、armv6、armv7和armv8系列。芯片類型有:arm7、arm9、arm11、cortex系列。指令集有:armv5、armv6和neon指令。關於ARM到知識參考:http://baike.baidu.com/view/11200.htm

最初的ARM指令集爲通用計算型指令集,指令集都是針對單個數據進行計算,沒有並行計算到功能。隨着版本的更新,後面逐漸加入了一些複雜到指令以及並行計算到指令。而NEON指令是專門針對大規模到並行運算而設計的。

NEON 技術可加速多媒體和信號處理算法(如視頻編碼/解碼、2D/3D 圖形、遊戲、音頻和語音處理、圖像處理技術、電話和聲音合成),其性能至少爲ARMv5 性能的3倍,爲 ARMv6 SIMD性能的2倍。

關於SIMD和SISD:Single Instruction Multiple Data,單指令多數據流。反之SISD是單指令單數據。以加法指令爲例,單指令單數據(SISD)的CPU對加法指令譯碼後,執行部件先訪問內存,取得第一個操作數;之後再一次訪問內存,取得第二個操作數;隨後才能進行求和運算。而在SIMD型的CPU中,指令譯碼後幾個執行部件同時訪問內存,一次性獲得所有操作數進行運算。這個特點使SIMD特別適合於多媒體應用等數據密集型運算。如下圖所示:

   

如何才能快速到寫出高效的指令代碼?這就需要對各個指令比較熟悉,知道各個指令的使用規範和使用場合。

ARM指令有16個32位通用寄存器,爲r0-r15,其中r13爲堆棧指針寄存器,r15爲指令計算寄存器。實際可以使用的寄存器只有14個。r0-r3一般作爲函數參數使用,函數返回值放在r0中。若函數參數超過4個,超過到參數壓入堆棧。

有效立即數的概念:每個立即數採用一個8位的常數(bit[7:0])循環右移偶數位而間接得到,其中循環右移的位數由一個4位二進制(bit[11:8] )的兩倍表示。如果立即數記作<immediate> , 8位常數記作immed_8 , 4位的循環右移值記作rotate_imm ,有效的立即數是由一個8位的立即數循環右移偶數位得到,可以表示成:

<immediate>=immed_8循環右移( 2×rotate_imm)

如:mov r4 , #0x8000 000A    #0x8000 000A 由0xA8循環右移0x2位得到。

下面介紹一些比較常用到一些指令。


內存訪問指令:

LDR和STR,有三種方式,比較容易搞混

LDR r0, [r1, #4]   r0 := mem[r1+4]   ,#4是直接偏移量,這時候只能在正負4Kb到範圍內。也可以是寄存器偏移,用+/-表示。記住r1不進行偏移。

LDR r0, [r1, #4]!  r0 :=mem[r1+4],r1 := r1 + 4,取值是取偏移量到值,並且r1進行偏移。

LDR r0, [r1], #4   r0 :=mem[r1] ,r1 := r1 +4,取值是取r1地方到值,取值後進行偏移。運算後自動加4,後變址。

另外:LDRB是無符號字節,SB是有符號字節,H無符號半字,SH有符號半字。

 

存儲器和寄存器數據交換:SWP,SWPB

如SWP r0, r1, [r2]   r0 := mem[r2],mem[r2] := r1

多寄存器數據傳輸:

LDMIA r1, {r0,r2,r5}  r0 = mem[r1], r2 = mem[r1+4], r5=mem[r1+8]

 

通用數據處理指令

第二操作數,常用到有LSR,LSL等,如mov r1, r2, lsl #2 將r2左移2位然後賦值到r1中。

常用到操作有ADD、SUB、AND、ORR、EOR、BIC、ORN,如果加上了S則會更新條件標記。

MOV移動,MVN取反移動。MOV可以是R寄存器,立即數以及接第二操作數。

REV:在字或半字內反轉字節或位到順序

MUL、MLA和MLS,乘法、乘加和乘減。MLA R1,R2,R3,R4表示R1=R2*R3+R4,還有有符號和無符號乘法等。

 

跳轉指令

B:無條件跳轉,BL:帶鏈接到跳轉,BX跳轉並交換指令集等。

 

重點介紹一下NEON指令,目前使用較多。而且使用難度也較大,很多文檔上都沒有比較詳細到介紹,也沒有給出相應到例子或者圖示。

 

一、NEON基本知識

NEON的寄存器:

有16個128位四字到寄存器Q0-Q15,32個64位雙子寄存器D0-D31,兩個寄存器是重疊的,在使用到時候需要特別注意,不小心就會覆蓋掉。如下圖所示:

兩個寄存器的關係:Qn =D2n和D2n+1,如Q8是d16和d17的組合。

 

NEON的數據類型:

注意數據類型針對到時操作數,而不是目標數,這點在寫的時候要特別注意,很容易搞錯,尤其是對那些長指令寬指令的時候,因爲經常Q和D一起操作。

 

NEON中的正常指令、寬指令、窄指令、飽和指令、長指令

正常指令:生成大小相同且類型通常與操作數向量相同到結果向量

長指令:對雙字向量操作數執行運算,生產四字向量到結果。所生成的元素一般是操作數元素寬度到兩倍,並屬於同一類型。L標記,如VMOVL。

寬指令:一個雙字向量操作數和一個四字向量操作數執行運算,生成四字向量結果。W標記,如VADDW。

窄指令:四字向量操作數執行運算,並生成雙字向量結果,所生成的元素一般是操作數元素寬度的一半。N標記,如VMOVN。

飽和指令:當超過數據類型指定到範圍則自動限制在該範圍內。Q標記,如VQSHRUN

 

二、NEON指令

NEON指令較多,下面主要介紹一些常見的指令用法。

 

複製指令:

VMOV:

兩個arm寄存器和d之間

vmov d0, r0, r1:將r1的內容送到d0到低半部分,r0的內容送到d0到高半部分

vmov r0, r1, d0:將d0的低半部分送到r0,d0的高半部分內容送到r1

一個arm寄存器和d之間

vmov.U32 d0[0], r0:將r0的內容送到d0[0]中,d0[0]指d0到低32位

vmov.U32 r0, d0[0]:將d0[0]的內容送到r0中

立即數:

vmov.U16 d0, #1:將立即數1賦值給d0的每個16位

vmov.U32 q0, #1:將立即數1賦值給q0的每個32位

長指令:VMOVL:d賦值給q

vmovl.U16 q0, d0:將d0的每個16位數據賦值到q0的每個32位數據中

窄指令:VMOVN:q賦值給d

vmovn.I32 d0, q0:將q0的每32位數據賦值到q0的每16位數據中

飽和指令:VQMOVN等,飽和到指定的數據類型

 vqmovun.S32 d0, q0:將q0到每個32位移動到d0中到每個16位中,範圍是0-65535

        

VDUP:

VDUP.8 d0, r0:將r0複製到d0中,8位

VDUP.16 q0, r0:將r0複製到q0中,16位

VDUP.32 q0, d2[0]:將d2的一半複製到q0中

VDUP.32 d0, d2[1]:將d2的一半複製到d0中

注意是vdup可以將r寄存器中的內容複製到整個neon寄存器中,不能將立即數進行vdup,立即數只能用vmov

 

邏輯運算

VADD:按位與;VBIC:位清除;VEOR:按位異或;VORN:按位或非;VORR:按位或

 

移位指令:

VSHL:左移、VSHLL:左移擴展、VQSHL:左移飽和、VQSHLU:無符號左移飽和擴展

VSHR:右移、VSHRN:右移窄、VRSHR:右移舍入、VQSHRUN:無符號右移飽和舍入

 

通用算術指令:

VABA:絕對值累加、VABD:絕對值相加、VABS:絕對值、VNEG:求反、VADD、VADDW、VADDL、VSUB、VSUBL、VSUBW:加減

VPADD:將兩個向量的相鄰元素相加

如VPADD.I16 {d2}, d0, d1


VPADDL:VPADDL.S16 d0, d1


VMAX:最大值,VMIN:最小值

VMUL、VMULL、VMLA(乘加)、VMLS(乘減)、

 

加載存儲指令:

VLD和VST



交叉存取的示意圖:

VREV反轉元素指令:

 

VEXT移位指令:

 

VTRN轉置指令:可以用於矩陣的轉置



VZIP指令:壓縮,類似交叉存取

VUZP指令:解壓操作,類似交叉存取


 

VTBL查表指令:從d0,d1中查找d3中的索引值,如果找到則取出,沒有找到則爲0,存入d2中

 

三、需要注意的地方

    load數據的時候,第一次load會把數據放在cache裏面,只要不超過cache的大小,下一次load同樣數據的時候,則會比第一次load要快很多,會直接從cache中load數據,這樣在彙編程序設計的時候是非常需要考慮的問題。

     如:求取一個圖像的均值,8*8的窗口,先行求和,然後列求和出來均值,這時候會有兩個函數,數據會加載兩遍,如果按照這樣去優化的話則優化不了多少。如果換成上面這種思路,先做行16行,然後再做列,這樣數據都在cache裏面,做列的時候load數據會很快。

   在做neon乘法指令的時候會有大約2個clock的阻塞時間,如果你要立即使用乘法的結果,則就會阻塞在這裏,在寫neon指令的時候需要特別注意。乘法的結果不能立即使用,可以將一些其他的操作插入到乘法後面而不會有時間的消耗。

如:vmul.u16 q1, d3, d4 

         vadd.u32 q1, q2, q3

此時直接使用乘法的結果q1則會阻塞,執行vadd需要再等待2個clock的時間

使用飽和指令的時候,如乘法飽和的時候,在做乘法後會再去做一次飽和,所以時間要比直接做乘法要慢。

如:  vmul.u16 q1, d3, d4

          vqmul.u32 q1, q2, q3

後一個的時間要比第一個的時間要久。

在對16位數據進行load或者store操作的時候,需要注意的是字節移位。比如是16位數據,則load 8個16位數據,如果指定寄存器進行偏移,此時需要特別注意。

例如:vld1.64 {d0}, [r0], r1

 

參考資料:

http://blogs.arm.com/software-enablement/277-coding-for-neon-part-4-shifting-left-and-right/

http://blogs.arm.com/software-enablement/161-coding-for-neon-part-1-load-and-stores/

http://blogs.arm.com/software-enablement/684-coding-for-neon-part-5-rearranging-vectors/


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章