1.簡介
讀過上一篇文章“ARM NEON快速上手指南”之后,相信你已經對ARM NEON編程有了基本的認識。但在真正利用ARM NEON優化程序性能時,還有很多編程技巧和注意事項。本文將結合本人的一些開發經歷,介紹NEON編程中的一些常見優化技巧,希望能對用戶在NEON實際開發中有些借鑒意義。
2.NEON優化技術
在利用NEON優化程序時,有下述幾項比較通用的優化技巧。
2.1 降低數據依賴性
在ARM v7-A NEON指令通常需要3~9個指令周期,NEON指令比ARM指令需要更多周期數。因此,為了減少指令延時,最好避免將當前指令的目的寄存器當作下條指令的源寄存器。如下例所示:
// C代碼
float SumSquareError_C(const float* src_a, const float* src_b, int count)
{
float sse = 0u;
int i;
for (i = 0; i < count; ++i) {
float diff = src_a[i] - src_b[i];
sse += (float)(diff * diff);
}
return sse;
}
// NEON實現一
float SumSquareError_NEON1(const float* src_a, const float* src_b, int count)
{
float sse;
asm volatile (
"veor q8, q8, q8
"
"veor q9, q9, q9
"
"veor q10, q10, q10
"
"veor q11, q11, q11
"
"1:
"
"vld1.32 {q0, q1}, [%0]!
"
"vld1.32 {q2, q3}, [%0]!
"
"vld1.32 {q12, q13}, [%1]!
"
"vld1.32 {q14, q15}, [%1]!
"
"subs %2, %2, #16
"
// q0, q1, q2, q3 是vsub的目的地寄存器.
// 也是vmla的源寄存器。
"vsub.f32 q0, q0, q12
"
"vmla.f32 q8, q0, q0
"
"vsub.f32 q1, q1, q13
"
"vmla.f32 q9, q1, q1
"
"vsub.f32 q2, q2, q14
"
"vmla.f32 q10, q2, q2
"
"vsub.f32 q3, q3, q15
"
"vmla.f32 q11, q3, q3
"
"bgt 1b
"
"vadd.f32 q8, q8, q9
"
"vadd.f32 q10, q10, q11
"
"vadd.f32 q11, q8, q10
"
"vpadd.f32 d2, d22, d23
"
"vpadd.f32 d0, d2, d2
"
"vmov.32 %3, d0[0]
"
: "+r"(src_a),
"+r"(src_b),
"+r"(count),
"=r"(sse)
:
: "memory", "cc", "q0", "q1", "q2", "q3", "q8", "q9", "q10", "q11","q12", "q13","q14", "q15");
return sse;
}
// NEON實現二
float SumSquareError_NEON2(const float* src_a, const float* src_b, int count)
{
float sse;
asm volatile (
"veor q8, q8, q8
"
"veor q9, q9, q9
"
"veor q10, q10, q10
"
"veor q11, q11, q11
"
"1:
"
"vld1.32 {q0, q1}, [%0]!
"
"vld1.32 {q2, q3}, [%0]!
"
"vld1.32 {q12, q13}, [%1]!
"
"vld1.32 {q14, q15}, [%1]!
"
"subs %2, %2, #16
"
"vsub.f32 q0, q0, q12
"
"vsub.f32 q1, q1, q13
"
"vsub.f32 q2, q2, q14
"
"vsub.f32 q3, q3, q15
"
"vmla.f32 q8, q0, q0
"
"vmla.f32 q9, q1, q1
"
"vmla.f32 q10, q2, q2
"
"vmla.f32 q11, q3, q3
"
"bgt 1b
"
"vadd.f32 q8, q8, q9
"
"vadd.f32 q10, q10, q11
"
"vadd.f32 q11, q8, q10
"
"vpadd.f32 d2, d22, d23
"
"vpadd.f32 d0, d2, d2
"
"vmov.32 %3, d0[0]
"
: "+r"(src_a),
"+r"(src_b),
"+r"(count),
"=r"(sse)
:
: "memory", "cc", "q0", "q1", "q2", "q3", "q8", "q9", "q10", "q11", "q12", "q13","q14", "q15");
return sse;
}
在NEON實現一中,我們把目的寄存器立刻當作源寄存器;在NEON實現二中,我們重新排布了指令,并給予目的寄存器盡量多的延時。經過測試實現二比實現一快30%。由此可見,降低數據依賴性對于提高程序性能有重要意義。一個好消息是編譯器能自動調整NEON intrinsics以降低數據依賴性。這個利用NEON intrinsics的一個很大優勢。
2.2 減少跳轉
NEON指令集沒有跳轉指令,當需要跳轉時,我們需要借助ARM指令。在ARM處理器中,分支預測技術被廣泛使用。但是一旦分支預測失敗,懲罰還是比較高的。因此我們最好盡量減少跳轉指令的使用。其實,在有些情況下,我們可以用邏輯運算來代替跳轉,如下例所示:
// C實現
if( flag )
{
dst[x * 4] = a;
dst[x * 4 + 1] = a;
dst[x * 4 + 2] = a;
dst[x * 4 + 3] = a;
}
else
{
dst[x * 4] = b;
dst[x * 4 + 1] = b;
dst[x * 4 + 2] = b;
dst[x * 4 + 3] = b;
}
// NEON實現
//dst[x * 4] = (a&Eflag) | (b&~Eflag);
//dst[x * 4 + 1] = (a&Eflag) | (b&~Eflag);
//dst[x * 4 + 2] = (a&Eflag) | (b&~Eflag);
//dst[x * 4 + 3] = (a&Eflag) | (b&~Eflag);
VBSL qFlag, qA, qB
ARM NEON指令集提供了下列指令來幫助用戶實現上述邏輯實現:
? VCEQ, VCGE, VCGT, VCLE, VCLT……
? VBIT, VBIF, VBSL……
減少跳轉,不僅僅是在NEON中使用的技巧,是一個比較通用的問題。即使在C程序中,這個問題也是值得注意的。
2.3 其它技巧
在ARM NEON編程時,一種功能有時有多種實現方式,但是更少的指令不總是意味著更好的性能,要依據測試結果和profiling數據,具體問題具體分析。下面列出來我遇到的一些特殊情況。2.3.1 浮點累加指令通常情況下,我們會用VMLA/VMLS來代替VMUL + VADD/ VMUL + VSUB,這樣使用較少的指令,完成更多的功能。但是與浮點VMUL相比,浮點VMLA/VMLS具有更長的指令延時,如果在指令延時中間不能插入其它計算的情況下,使用浮點VMUL + VADD/ VMUL + VSUB反而具有更好的性能。一個真實例子就是Ne10庫函數的浮點FIR函數。代碼片段如下所示:
實現1:在兩條VMLA指令之間,僅有VEXT指令。而根據指令延時表,VMLA需要9個周期。
實現2:對于qAcc0,依然存在指令延時。但是VADD/VMUL只需要5個周期。下列代碼中周期n粗略地表示了指令執行需要的周期數。與實現1相比,實現2節省了6個周期。性能測試也表明實現2具有更好的性能。
實現 1: VMLA
VEXT qTemp1,qInp,qTemp,#1
VMLA qAcc0,qInp,dCoeff_0[0]-- cycle 0
VEXT qTemp2,qInp,qTemp,#2
VMLA qAcc0,qTemp1,dCoeff_0[1] -- cycle 9
VEXT qTemp3,qInp,qTemp,#3
VMLA qAcc0,qTemp2,dCoeff_1[0] -- cycle 18
VMLA qAcc0,qTemp3,dCoeff_1[1] -- cycle 27
得到最終結果 qAcc0需要36個指令周期。
實現 2: VMUL+VADD
VEXT qTemp1,qInp,qTemp,#1
VMLA qAcc0,qInp,dCoeff_0[0] ]-- cycle 0
VMUL qAcc1,qTemp1,dCoeff_0[1]
VEXT qTemp2,qInp,qTemp,#2
VMUL qAcc2,qTemp2,dCoeff_1[0]
VADD qAcc0, qAcc0, qAcc1-- cycle 9
VEXT qTemp3,qInp,qTemp,#3
VMUL qAcc3,qTemp3,dCoeff_1[1]
VADD qAcc0, qAcc0, qAcc2-- cycle 14
VADD qAcc0, qAcc0, qAcc3-- cycle 19
得到最終結果 qAcc0需要24個指令周期。
與實現1相比,三條VADD指令需要6個發射指令周期。總共需要 30個指令周期。
modules/dsp/NE10_fir.neon.s:line 195
指令延時請參考下表:
Name | Format | Cycles | Result |
---|---|---|---|
VADD/VSUB/VMUL | Qd,Qn,Dm | 2 | 5 |
VMLA/VMLS | Qd,Qn,Dm | 2 | 9 |
表格來源于Cortex-A9 NEON Media Processing Engine Revision: r4p1 Technical Reference Manual: 3.4.8。
表格中:? Cycles:指令發射時間
? Result:指令執行時間
2.4 小結
總結起來,NEON的優化技巧主要有以下幾點
? 盡量利用指令執行延時,合理安排指令順序
? 少用跳轉
? 注意cache命中率
審核編輯:郭婷
-
ARM
+關注
關注
134文章
9057瀏覽量
366874 -
寄存器
+關注
關注
31文章
5325瀏覽量
120052
原文標題:Arm NEON學習(二)優化技術
文章出處:【微信號:Ithingedu,微信公眾號:安芯教育科技】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論