ARM彙編優化
要做程序的優化,最徹底的方法當然是彙編!還有除了彙編以外(除了二進制)能讓你對你的處理器有更全面的控制嗎?!對於ARM彙編,作爲一個初學者,也就只好先補補基礎了@_@。
首先,程序段的定義從AREA 開始,它命名一個代碼區域,注意,用非阿拉伯數字作爲名字時,應該用|把名字包起來,CODE關鍵字聲明程序(猜測),readonly聲明訪問權限(猜測)。EXPORT 來表示某個可以用作外部連接的符號(簡單點,應該就是函數名?)。END用來結尾。
#eg:
AREA |.text|, CODE, READONLY
EXPORT square
; int square(int i)
square ;armcc把不縮進的正文作爲一個標號定義
MUL r1,r0,r0
MOV r0,r1 ;ARM乘法指令有一個限制,就是目標寄存器不能和第一個參數寄存器相同
MOV pc,lr ;對Thumb指令,應該改爲BX lr
END
使用import,可以聲明其他文件中定義的標號,要用ARM C庫的話,就import |Lib$Request$armlib|, WEAK表示本行的標號如果找不到,不會報告連接錯誤。如果程序包含主程序main,那麼要引入標號__main,代表C庫初始化的開始。RN可以讓用戶給寄存器命名。
#eg:
AREA |.text|, CODE,READONLY
EXPORT main
IMPORT |Lib$$Request$$armlib|, WEAK
IMPORT __main ;C library entry
IMPORT printf ;prints to stdout
i RN 4
;int main(void)
main
STMFD sp!,{i,lr}
MOV i,#0
loop
ADR r0,print_string
MOV r1,i
MUL r2,i,i
BL printf
ADD i,i,#1
CMP i,#10
BLT loop
LDMFD sp!,{i,pc}
print_string
DCB " Square of %d is %d/n", 0
END
MAP(別名^)和FIELD(別名#),可以在堆棧中爲變量和數組定義和分配空間。
MAP 0 ;map symbols to offsets starting at offset 0?
a FIELD 4 ;a is 4 bytes integer(at offset 0)
b FIELD 2 ;b is bytes integer(at offset 4)
c FIELD 64 ;c is an array of 64 characters (at offset 6)
length FIELD 0 ;length records the current offset reached?
MACRO用來聲明宏:
MACRO
CHECKSUM $ alignment
checksum_$ alignment
LDR w,[data],#4
l0 ;loop
IF $ alignment<>0
ADD sum,sum,w,LSR #8, $alignement
LDR w,[data],#4
SUBS N,N,#1
ADD sum,sum,w,LSL#32-8* $ alignment
ELSE
ADD sum,sum,w
LDR w,[data],#4
SUBS N,N,#1
ENDIF
BGT %BT l0
MOV pc,lr
MEND
針對彙編的優化主要是面向硬件的。首先是流水線,有些load指令要需要多個週期來完成,可以通過調整指令的順序(當然要保證邏輯)來改善性能。另外,儘量讓程序只是用寄存器,方法是搞清楚數據佔用寄存器的時間關係,實現寄存器有效的分時複用。另外,可以將長度較小的變量合併到一個32位寄存器中保存,以節省寄存器。由於PC可以通過程序操作,對於條件指令,可以直接用PC與形成分支的參數作運算來尋找對應的分支:
;int switch_relative(int x)
switch_relative
CMP x,#8
ADDLT pc,pc,x,LSL,#2
B method_d ;利用流水線,如果PC還是按順序那麼default分支的預取址就不
;會被沖掉
B method_0
B method_1
ARM上的C編程
1.arm c編譯器默認char類型是8位無符號的,與其它編譯器有點不同
2.局部變量最好用int型,因爲寄存器是32位的,如果變量不是32位的就需要額外的指令限制範圍.
例如: 變量i,操作i++ ,如果int i, 則只需add r1,r1,#1 如果char i,則變成add r1,r1,#1
and r1,r1, 0xff .多了一條指令
3.循環最好用do{}while()型的,相比for(;;)型循環每次循環可以節省3條指令
4.函數參數也最好用int 型的,例如 short add(short x,short y)
編譯器爲了保證輸入參數的是short型的會添加額外的指令,比如確保x是short型的,需要
mov r0,r0,lsr #16 mov r0,r0,asr #16
5.函數參數最好不要超過4個,因爲前4個參數是通過寄存器r0-r3傳遞的,超過4個後的參數使用堆棧傳遞,速度慢多了.
6.適當的展開循環.循環有一定的開銷,在一個循環中多做幾遍操作,減少循環的次數可以減少循環的開銷.
例如: i = 0; int i;
do do{
{ i++;
i++; i++;
}while(i<64) 改爲 i++;}while(i<64)
當然,這樣做也增加了代碼長度.
7.使用減計數到0的循環結構,這樣就不用用寄存器保存終止值.
8.使用無符號的循環計數值,循環條件是i!=0,而不是i>0, 這樣循環的開銷只有2條指令
ARM上的彙編優化小方法
1. 加減法,邏輯操作佔一個週期,目的地址是PC寄存器時增加一個週期。分支指令佔3個週期。在cache命中的情況下,16位和8位的裝載指令(LDRH、LDRH等)佔一個週期,但緊跟的2個週期不能使用裝入的數據。32位裝載指令佔一個週期,緊跟的一個週期不能使用裝載數據。如果裝載入PC,同樣要增加2個週期。
LDR r1,[r2] ADD r1,r1,r3 ADD r4,r4,r5 佔4個週期
改變次序後
LDR r1,[r2] ADD r4,r4,r5 ADD r1,r1,r3 佔3個週期
2. load指令佔時間比較長,在循環中可以使用預載的方法將load與跳轉指令放在一起,減少流水線的斷流。
例如:
loop
LDRB r2,[r1]
............... //do
B loop
更改爲
LDRB r2,[r1]
loop
.............. //do
LDRB r2,[r1]
B loop
3. 循環展開時,可以在計算i步時就加載i+1步的數據,在i步的結果還沒準備好時執行i+1步計算。
4. ARM只有16個可見寄存器,其中14個通用寄存器,1個堆棧指針r13,1個程序計數器r15。在圖像處理的應用中很多是8位的操作數,可以利用32爲寄存器一次進行兩組運算。
例如:加操作 100 + 50 和 2 + 3
位 24 16 8 0
操作數1 0 100 0 2
操作數2 0 50 0 3
結果 0 150 0 5
5. 寄存器數量不夠時,可用32爲寄存器保存兩個16位變量和4個8位變量。
初入嵌入式軟件開發不久,最近在看UC/OS2的內核源碼,感覺很有意思,記下一些學習過程,歡迎大家拍磚。
嵌入式軟件經常要同時完成若干任務,可以在無人干預的情況下應對所有的事件及異常,並且可以根據事件的輕重緩急自動保證最先完成最緊急的任務。
嵌入式軟件由RTOS跟其上跑的應用部分軟件組成,應用部分軟件可簡單看成一個個任務,每個任務可以對相關的外界產生的事件或是異常響應。而RTOS的核心功能就是管理各個任務,並建立起任務和外界事件的聯繫。
一個典型的任務示意如下:
void Task()
{
for (;;)
{
//Do something initial;
OSFlagPend();
OSMboxPend();
OSMutexPend();
OSQPend();
OSSempend();
OSTaskPend(priority);
OSTaskDel(priority);
OSTimeDly();
/* application code */
}
}
首先任務應該包含一個無限循環,這個循環意在一直處理跟任務自己感興趣的事件。可以看到上面的示意代碼裏有一串的Pend調用,這些Pend函數就是每個RTOS都會提供的,使用了這些Pend調用後RTOS就可以讓這個任務與它感興趣的事件建立起聯繫。如果有它感興趣的事件發生,就會向下執行到application code來處理這個事件。處理完以後又進入循環回等待它感興趣事件的狀態,直到有它感興趣的事件發生,然後又處理,如此周而復此。如果沒有這個任務感興趣事件發生會怎麼呢?那RTOS就會掛起這個任務,去執行其它的任務處理其自身感興趣的事件。如果沒有任何事件發生會怎麼樣呢?這種情況下通常RTOS會運行一個自建的任務TaskIdle(), 這個任務什麼事也不幹,通常是給一個整數進行自加動作。
上面就是嵌入式軟件開發最根本的東西,但RTOS是究竟是怎樣工作的呢?後續日子裏我們再一起學習分享吧。^0^