摘要: 本文主要對Arm Compiler 6編譯器的優(yōu)化循環(huán)對編寫優(yōu)化代碼的作用進(jìn)行介紹。
關(guān)鍵字:Arm Compiler 6、編譯器、優(yōu)化循環(huán) 、循環(huán)展開、pragma、循環(huán)向量化、循環(huán)終止、無限循環(huán)、
1. 循環(huán)展開
循環(huán)執(zhí)行的時(shí)間取決于循環(huán)的次數(shù),循環(huán)中每次檢查是否進(jìn)行循環(huán)的條件會(huì)降低循環(huán)的性能。使用循環(huán)展開可以減少檢查條件的判斷次數(shù),但是展開循環(huán)就意味著增加代碼量。例如:在精確的時(shí)鐘周期循環(huán)中,可以使用#pragma unroll (n)來展開循環(huán)。
“pragma”(編譯指示)僅在選擇優(yōu)化等級為-O2/-O3/-Ofast和-Omax時(shí)有效。
編譯指示的相關(guān)用法:
#pragma unroll (n) | 展開n次循環(huán) |
#pragma unroll_completely | 展開所有循環(huán) |
注:雖然給出了循環(huán)展開的編譯指示,但Arm官方不建議使用,這樣會(huì)影響編譯器的展開優(yōu)化和其他循環(huán)優(yōu)化。
不使用循環(huán)展開的代碼 | 使用循環(huán)展開的代碼 |
int countSetBits1(unsigned int n) { int bits = 0; while (n != 0) { if (n & 1) bits++; n >>= 1; } return bits; } | int countSetBits2(unsigned int n) { int bits = 0; #pragma unroll (4) while (n != 0) { if (n & 1) bits++; n >>= 1; } return bits; } |
將代碼分別復(fù)制到file.c文件中,然后使用以下命令進(jìn)行編譯和反匯編。
armclang --target=arm-arm-none-eabi -march=armv8-a file.c -O2 -S -o file.s
不使用循環(huán)展開的匯編代碼 | 使用循環(huán)展開的匯編代碼 |
countSetBits1: mov r1, r0 mov r0, #0 cmp r1, #0 bxeq lr mov r2, #0 mov r0, #0 .LBB0_1: and r3, r1, #1 cmp r2, r1, asr #1 add r0, r0, r3 lsr r3, r1, #1 mov r1, r3 bne .LBB0_1 bx lr | countSetBits2: mov r1, r0 mov r0, #0 cmp r1, #0 bxeq lr mov r2, #0 mov r0, #0 LBB0_1: and r3, r1, #1 cmp r2, r1, asr #1 add r0, r0, r3 beq .LBB0_4 @ BB#2: asr r3, r1, #1 cmp r2, r1, asr #2 and r3, r3, #1 add r0, r0, r3 asrne r3, r1, #2 andne r3, r3, #1 addne r0, r0, r3 cmpne r2, r1, asr #3 beq .LBB0_4 @ BB#3: asr r3, r1, #3 cmp r2, r1, asr #4 and r3, r3, #1 add r0, r0, r3 asr r3, r1, #4 mov r1, r3 bne .LBB0_1 .LBB0_4: bx lr |
可以看到展開循環(huán)時(shí),代碼執(zhí)行會(huì)更快,但代碼量也更大。
2. 循環(huán)向量化
如果編譯的目標(biāo)含有SIMD單元,那么編譯器就可以使用向量引擎來優(yōu)化代碼的向量部分。在優(yōu)化等級為-O1,可以使用-fvectorize 來啟動(dòng)優(yōu)化,而在-O2或更高等級時(shí)向量優(yōu)化是自動(dòng)啟用。
要使用向量優(yōu)化,在編寫代碼的時(shí)候需要將結(jié)構(gòu)體的成員放到同一個(gè)循環(huán)中,而不能使用獨(dú)立的循環(huán)。
可以進(jìn)行SIMD優(yōu)化的代碼 | 不能進(jìn)行SIMD優(yōu)化的代碼 |
typedef struct tBuffer { int a; int b; int c; } tBuffer; tBuffer buffer[8]; void DoubleBuffer1 (void) { int i; for (i=0; i<8; i++) { buffer[i].a *= 2; buffer[i].b *= 2; buffer[i].c *= 2; } } | typedef struct tBuffer { int a; int b; int c; } tBuffer; tBuffer buffer[8]; void DoubleBuffer2 (void) { int i; for (i=0; i<8; i++) buffer[i].a *= 2; for (i=0; i<8; i++) buffer[i].b *= 2; for (i=0; i<8; i++) buffer[i].c *= 2; } |
對于每個(gè)例子,將代碼分別復(fù)制到file.c文件中,然后使用以下命令進(jìn)行編譯和反匯編。
armclang --target=arm-arm-none-eabi -march=armv8-a file.c -O2 -S -o file.s
向量優(yōu)化后匯編代碼 | 未進(jìn)行向量優(yōu)化的代碼 |
DoubleBuffer1: .fnstart @ BB#0: movw r0, :lower16:buffer movt r0, :upper16:buffer vld1.64 {d16, d17}, [r0:128] mov r1, r0 vshl.i32 q8, q8, #1 vst1.32 {d16, d17}, [r1:128]! vld1.64 {d16, d17}, [r1:128] vshl.i32 q8, q8, #1 vst1.64 {d16, d17}, [r1:128] add r1, r0, #32 vld1.64 {d16, d17}, [r1:128] vshl.i32 q8, q8, #1 vst1.64 {d16, d17}, [r1:128] add r1, r0, #48 vld1.64 {d16, d17}, [r1:128] vshl.i32 q8, q8, #1 vst1.64 {d16, d17}, [r1:128] add r1, r0, #64 add r0, r0, #80 vld1.64 {d16, d17}, [r1:128] vshl.i32 q8, q8, #1 vst1.64 {d16, d17}, [r1:128] vld1.64 {d16, d17}, [r0:128] vshl.i32 q8, q8, #1 vst1.64 {d16, d17}, [r0:128] bxlr | DoubleBuffer2: .fnstart @ BB#0: movw r0, :lower16:buffer movt r0, :upper16:buffer ldr r1, [r0] lsl r1, r1, #1 str r1, [r0] ldr r1, [r0, #12] lsl r1, r1, #1 str r1, [r0, #12] ldr r1, [r0, #24] lsl r1, r1, #1 str r1, [r0, #24] ldr r1, [r0, #36] lsl r1, r1, #1 str r1, [r0, #36] ldr r1, [r0, #48] lsl r1, r1, #1 str r1, [r0, #48] ldr r1, [r0, #60] lsl r1, r1, #1 str r1, [r0, #60] ldr r1, [r0, #72] lsl r1, r1, #1 str r1, [r0, #72] ldr r1, [r0, #84] lsl r1, r1, #1 str r1, [r0, #84] ldr r1, [r0, #4] lsl r1, r1, #1 str r1, [r0, #4] ldr r1, [r0, #16] lsl r1, r1, #1 ... bx lr |
在64位運(yùn)行狀態(tài)下要避免編譯器使用SIMD向量優(yōu)化可以在-march或-mcpu后+nosimd;
例如:
armclang --target=aarch64-arm-none-eabi -march=armv8-a+nosimd -O2 file.c -S -o file.s
在32位運(yùn)行狀態(tài)下要避免編譯器使用SIMD向量優(yōu)化,可以通過設(shè)置-mfpu=fp-armv8;
例如:
armclang --target=aarch32-arm-none-eabi -march=armv8-a -mfpu=fp-armv8 -O2 file.c -S -o file.s
3. 循環(huán)終止
在寫循環(huán)的時(shí)候如果編寫不當(dāng)會(huì)使得代碼的運(yùn)行效率降低和代碼量增大。建議使用以下的終止條件:
1)使用變量類型為:unsigned int
2)使用向下減少的計(jì)數(shù)方式,以減到0作為計(jì)數(shù)結(jié)束。
3)使用簡單的終止條件。
單獨(dú)或組合使用以上原則的終止條件,可以獲得更好的代碼大小或效率。
例如:這是一個(gè)實(shí)現(xiàn)n!的計(jì)算程序。
遞增循環(huán) | 遞減循環(huán) |
int fact1(int n) { int i, fact = 1; for (i = 1; i <= n; i++) fact *= i; return (fact); } | int fact2(int n) { unsigned int i, fact = 1; for (i = n; i != 0; i--) fact *= i; return (fact); } |
用以下命令反匯編以下armclang -Os -S --target=arm-arm-none-eabi -march=armv8-a
遞增循環(huán) | 遞減循環(huán) |
fact1: mov r1, r0 mov r0, #1 cmp r1, #1 bxlt lr mov r2, #0 .LBB0_1: add r2, r2, #1 mul r0, r0, r2 cmp r1, r2 bne .LBB0_1 bx lr | fact2: mov r1, r0 mov r0, #1 cmp r1, #0 bxeq lr .LBB1_1: mul r0, r0, r1 subs r1, r1, #1 bne .LBB1_1 bx lr |
對比反匯編代碼可以看出在遞減循環(huán)中用SUBS指令代替了遞增循環(huán)中ADD 和CMP兩條指令。這是因?yàn)镾UBS指令會(huì)自動(dòng)更新Z標(biāo)志。
此外在遞減循環(huán)中變量n不必再循環(huán)的過程實(shí)時(shí)使用,從而減少了寄存器的數(shù)量。
如果終止條件是一個(gè)函數(shù),則循環(huán)的每次都調(diào)用該函數(shù),這種情況下遞減的循環(huán)優(yōu)勢就更明顯了。例如:
for (...; i < get_limit(); ...);
說明:這種遞減循環(huán)計(jì)數(shù)的方式也適用于while-do 命令。
4. 無限循環(huán)
在某些情況下armclang會(huì)刪除一些編譯器認(rèn)為沒有影響的無限循環(huán),從而導(dǎo)致最終程序無法正常運(yùn)行。
為確保無限循環(huán)的正確編譯執(zhí)行,ARM官方建議在無限循環(huán)中添加__arm volatile的聲明。這個(gè)聲明的目的是告訴編譯器刪除這個(gè)無限循環(huán)會(huì)有影響,不能被優(yōu)化刪除。在無限循環(huán)中,把處理器設(shè)置為低功耗模式是一個(gè)不錯(cuò)的做法,當(dāng)有中斷或事件觸發(fā)時(shí)再回到正常模式。
下面是一個(gè)包含__arm volatile聲明的無限循環(huán)例子:
void infinite_loop(void) {
while (1)
??__asm volatile("wfe");
}
注:wfe(Wait for Event)是給處理器一個(gè)提示,使處理器進(jìn)入低功耗狀態(tài),直到事件或中斷觸發(fā)。
來源:《Arm? Compiler for Embedded User Guide Version 6.18》
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
關(guān)于億道電子
億道電子技術(shù)有限公司(英文名稱:Emdoor Electronics Technology Co.,Ltd)是國內(nèi)資深的研發(fā)工具軟件提供商,公司成立于 2002 年,面向中國廣大的制造業(yè)客戶提供研發(fā)、設(shè)計(jì)、管理過程中使用的各種軟件開發(fā)工具,致力于幫助客戶提高研發(fā)管理效率、縮短產(chǎn)品設(shè)計(jì)周期,提升產(chǎn)品可靠性。
20 年來,先后與 Altium、ARM、Ansys、QT、Adobe、Visu-IT、Minitab、Testplant、EPLAN、HighTec、GreenHills、PLS、Ashling、MSC Software 、Autodesk、Source Insight、TeamEDA、MicroFocus等多家全球知名公司建立戰(zhàn)略合作伙伴關(guān)系,并作為他們在中國區(qū)的主要分銷合作伙伴服務(wù)了數(shù)千家中國本土客戶,為客戶提供從芯片級開發(fā)工具、EDA 設(shè)計(jì)工具、軟件編譯以及測試工具、結(jié)構(gòu)設(shè)計(jì)工具、仿真工具、電氣設(shè)計(jì)工具、以及嵌入式 GUI 工具等等。億道電子憑借多年的經(jīng)驗(yàn)積累,真正的幫助客戶實(shí)現(xiàn)了讓研發(fā)更簡單、更可靠、更高效的目標(biāo)。
![](/Uploads/2022-08-16/62fb65a3cb6de.png)
歡迎關(guān)注“億道電子”公眾號
了解更多研發(fā)工具軟件知識