ARM和neon指令集
在移動平臺上進行一些複雜演算法的開發,一般需要用到指令集來進行加速。目前在移動上使用最多的是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
參考資料: