ARM處理器NEON編程及優化技巧——處理剩余的元素
關鍵字: ARM NEON Cortex-A8 cache 對齊
本文引用地址:http://www.104case.com/article/201611/317425.htm剩余的元素Leftovers
通常NEON會向量處理從4個到16個元素長度的數據,如果你發現你的數組不是這個這個長度的整數倍,你就需要單獨處理那些剩下來的幾個元素。如你每次可以使用NEON來加載處理并存儲8個元素的數據,但是你的數組有21個元素,你就需要先迭代兩次,然后第三次,你只剩下5個元素,此時應該如何處理呢?
Fixing Up修復處理方式
有三種處理方式來處理剩下來的元素,這些方法的需求、性能和代碼大小不同,下面順序介紹,從速度最快的方法開始。
Larger Arrays更大的數組
如果改變你要處理的數組大小,比如增加數組大小到向量大小的整數倍,這樣就能在最后一次數據處理時也按照向量大小處理而不會把臨近的數據損壞。如上面的例子里,把數組大小增加到24個元素,這樣就能用NEON用3次迭代完成所有的數據處理而不會損壞周邊數據。
圖1. 填補數組到向量的整數個大小
注意事項Notes
- 分配更大的數組需要更多的存儲空間,這會增加相當大的空間如果包含非常多的短數組;
- 在數組后面填補的數據元素需要初始化為一個不會影響到結果的值,例如你要做加法,那這個新元素需要初始化為0以影響計算結果。
- 一些情況下,可能沒法初始化填充的數據,無論填充什么都會影響計算的結果;
Code Fragment代碼片段實例
@ r0 是輸入的數組指針;
@ r1 是輸出數組指針;
@ r2 是數組數據的長度;
假設數組長度大于0,是向量大小的整數倍,并且大于或者等于數組的長度;
add r2, r2, #7 @ 數據長度加上向量長度-1
lsr r2, r2, #3 @ 把數組長度變成向量個數,即除以向量大小8
loop:
subs r2, r2, #1 @ 減少循環計數器個數
vld1.8 {d0}, [r0]! @ 從數組加載8個元素,從地址r0到寄存器d0,然后更新地址寄存器r0到下一個向量地址;
...
... @ 處理在d0寄存器的數據
...
vst1.8 {d0}, [r1]! @ 把8個結果元素保存到輸出數組,更新地址r1到下一個向量
bne loop @ 如果r2不等于0,繼續循環
Overlapping重疊計算
如果進行數據處理的操作合適的話,可以考慮把剩余部分的元素通過重疊計算的方式處理,這就會把某些重疊部分的元素計算兩次。如下面的例子里,第一次迭代計算元素0到7,第一次計算5到12,第三次計算13到20。從而第一次計算和第二次計算重疊的元素5到7就被計算了兩次。
arm.com/index.php?app=core&module=attach§ion=attach&attach_rel_module=blogentry&attach_id=419" rel="nofollow" >
圖2. 重疊向量,在橙色區域的數據計算兩次
Notes需要事項
- 重疊處理只適用于需要處理的數組長度不會隨著每次迭代而改變的情況,但不適用于每次迭代結果改變的情況,如累加計算,這樣重疊部分的數據會被計算兩次;
- 數組內元素的個數至少大于一次完整迭代的向量大小;
Code Fragment代碼片段實例
@ r0 是輸入的數組指針;
@ r1 是輸出數組指針;
@ r2 是數組數據的長度;
假設數據操作冪等,并且數組長度大于等于一個向量大小長度。
ands r3, r2, #7 @ 計算每次處理完整個向量后剩余元素個數,使用與操作
beq loopsetup @ 如果剩余元素個數為0,則數組長度是整數個向量大小,不用重疊計算,單獨處理第一個元素部分
vld1.8 {d0}, [r0], r3 @ 加載數組第一個向量,然后更新數組大小為剩余元素個數r3內保持
...
... @ 處理d0寄存器內的輸入數據
...
vst1.8 {d0}, [r1], r3 @ 保持8個元素到輸出數組,更新指針,然后開始處理循環
loopsetup:
lsr r2, r2, #3 @ 把數組長度除以8,計算循環迭代次數,若干元素跟第一次迭代的重疊
loop:
subs r2, r2, #1 @ 減少循環計數器個數
vld1.8 {d0}, [r0]! @ 從數組加載8個元素,從地址r0到寄存器d0,然后更新地址寄存器r0到下一個向量地址;
...
... @ 處理在d0寄存器的數據
...
vst1.8 {d0}, [r1]! @ 把8個結果元素保存到輸出數組,更新地址r1到下一個向量
bne loop @ 如果r2不等于0,繼續循環
單個元素的計算過程Single Elements
NEON提供了能處理向量里的單一元素的加載和存儲指令,用這些指令,你能加載包含一個元素的部分向量,處理它然后把結果保存到內存。如下面的例子,前兩次的迭代處理跟前面類似,處理元素0到7以及8到15,剩下的5個元素可以在第三次迭代處理,加載處理并存儲單一的元素。
圖3. 處理單一的元素實例
注意事項
- 這種方法比前面的兩種方法速度要慢,每個元素的處理都需要單獨進行;
- 這種的剩余元素處理方法需要兩個迭代循環,第一個處理向量的循環,還有處理剩余元素的循環,這會增加代碼大??;
- NEON的單一元素加載只改變目標元素的值,而保留其他的元素不變,如果你向量計算的指令會在一個向量間反復計算,如VPADD,這些寄存器需要在第一個元素加載時初始化。
代碼片段
@ r0 是輸入的數組指針;
@ r1 是輸出數組指針;
@ r2 是數組數據的長度;
lsrs r3, r2, #3 @ 計算向量循環迭代的次數
beq singlesetup @ 如果沒有完整的一次迭代向量計算,則跳轉到單一元素處理循環
@ 處理向量循環
vectors:
subs r3, r3, #1 @減少循環計數器個數
vld1.8 {d0}, [r0]! @ 從數組加載8個元素,從地址r0到寄存器d0,然后更新地址寄存器r0到下一個向量地址;
...
... @ 處理在d0寄存器的數據
...
vst1.8 {d0}, [r1]! @ 把8個結果元素保存到輸出數組,更新地址r1到下一個向量
bne vectors @如果r3不等于0,繼續循環
singlesetup:
ands r3, r2, #7 @ 計算單一元素迭代的次數
beq exit @ 如果單一元素計算次數為0,則跳轉退出
@ 處理單一元素的循環
singles:
subs r3, r3, #1 @減少循環計數器個數
vld1.8 {d0[0]}, [r0]! @從數組加載單一元素,從地址r0到寄存器d0,然后更新地址寄存器r0到下一個地址
...
... @ 處理在d0[0]內的輸入數據
...
vst1.8 {d0[0]}, [r1]! @ 保存單一元素結果到輸出數組,更新指針地址
bne singles @如果r3不等于0,繼續循環
exit:
其他的考慮
在開始處還是結束處
用重疊計算的方式以及用單一元素處理都能在數組開始處或者結束處處理,因而代碼就要考慮兩種實現方式哪種效率高些,哪個更適合你的系統應用。
數據對齊
加載或者存儲指令的地址應該對齊到cache line,這樣內存的訪問效率更高。這樣就需要在Cortex-A8的處理器上至少16字對齊,如果你不能把輸入和輸出數組的起始地址對齊到16字,你就必須處理開始和結束數據處理的那若干個元素以使得后續的數據訪問是對齊到cache行的。為了使用內存對齊的方式訪問內存以提高速度,你在使用NEON指令時需要使用諸如64或者128或者256等地址限定符來制定加載和存儲指令。你可以比較發出一個對齊的訪問和非對齊訪問的性能,以下是始終周期的頁面Cortex-A8 TRM.
使用ARM來做修復
在使用單個元素處理的情況下,你可以使用ARM指令來進行單個元素的操作,但是同時使用ARM和NEON來訪問同一塊區域的內存會降低系統性能,因為從ARM的流水線發出的寫操作會在NEON的流水線完成之后才能進行。因而你要盡量的避免在ARM和NEON的代碼里同時訪問同一塊內存區域(當然,這同一塊內存區域也對應于同一個cache line)
評論