C51優(yōu)化設計之循環(huán)語(yǔ)句
程序1:unsigned char i;unsigned char sum;for(i=1,sum=0;i<11;i++){sum+=i;}匯編代碼為:C:0x0003 7F01 MOV R7,#0x01C:0x0005 E4 CLR AC:0x0006 FE MOV R6,AC:0x0007 EF MOV A,R7C:0x0008 2E ADD A,R6C:0x0009 FE MOV R6,AC:0x000A 0F INC R7C:0x000B BF0BF9 CJNE R7,#0x0B,C:0007代碼長(cháng)度(字節):11,執行周期(機器周期):63程序2:unsigned char i;unsigned char sum;for(i=10,sum=0;i;i--){sum+=i;}匯編代碼為:C:0x000F 7F0A MOV R7,#0x0AC:0x0011 E4 CLR AC:0x0012 FE MOV R6,AC:0x0013 EF MOV A,R7C:0x0014 2E ADD A,R6C:0x0015 FE MOV R6,AC:0x0016 DFFB DJNZ R7,C:0013代碼長(cháng)度(字節):9,執行周期(機器周期):53程序3:unsigned char i=11;unsigned char sum=0;while(i--){sum+=i;}匯編代碼為:C:0x0003 7F0A MOV R7,#0x0BC:0x0005 E4 CLR AC:0x0006 FE MOV R6,AC:0x0007 AD07 MOV R5,0x07C:0x0009 1F DEC R7C:0x000A ED MOV A,R5C:0x000B 6005 JZ C:0012C:0x000D EF MOV A,R7C:0x000E 2E ADD A,R6C:0x000F FE MOV R6,AC:0x0010 80F5 SJMP C:0007代碼長(cháng)度(字節):15,執行周期(機器周期):130
從以上三個(gè)不同程序可以看出,其運算結果都是0x37(55),但最短代碼為9,最長(cháng)代碼為15,最快速度為53,最慢速度為130,可見(jiàn)三個(gè)程序的性能差異較大.
本文引用地址:http://dyxdggzs.com/article/201611/323326.htm如何編出占用空間小運行效率高的循環(huán)代碼呢?在C51編譯環(huán)境下要寫(xiě)出優(yōu)秀的循環(huán)代碼必須熟悉51匯編語(yǔ)言的指令系統.觀(guān)察程序2,循環(huán)控制指令使用了DJNZ循環(huán)轉移指令,該指令同時(shí)完成計數和循環(huán)判斷兩種操作,而且只占用兩個(gè)字節,是51指令系統中最為高效的循環(huán)指令,因此在設計循環(huán)程序時(shí),應盡可能使C51將DJNZ用于循環(huán)程序中.當然DJNZ指令的循環(huán)次數是確定的,主要用在有確定循環(huán)次數的情況.
DJNZ指令的一個(gè)最大特點(diǎn)是遞減計數,因此循環(huán)程序必須采用遞減方式才有可能編譯出DJNZ指令,如以上程序2.DJNZ指令的另一個(gè)特點(diǎn)是先減后判斷,因此設計循環(huán)程序也必須堅持先減后判斷的原則,否則得不到DJNZ指令,如以上程序3.如果將程序3改寫(xiě)為:
unsigned char i=10;
unsigned char sum=0;
while(i)
{
sum+=i;
i--;
}
就可以得到與程序2相同的匯編代碼.若i--后還有其它操作,比如改為:
unsigned char i=10,j=0;
unsigned char sum=0;
while(i)
{
sum+=i;
i--;
j++;
}
也得不到DJNZ匯編指令,也就是說(shuō),循環(huán)語(yǔ)句在執行過(guò)程中,減1與判斷必須是連續的,且減1在前,判斷在后.對于while循環(huán),當將減1與判斷合成一步時(shí),應當采用while(--i).按照以上所述,do-while循環(huán)同樣可以匯編出DJNZ指令,不再一一列舉.
但是當循環(huán)變量不是通過(guò)常數賦值語(yǔ)句完成,而是來(lái)自于另一個(gè)變量時(shí),for和while語(yǔ)句無(wú)論采用何種控制流程都不能產(chǎn)生DJNZ指令,因為這兩種循環(huán)都是先判斷后執行的控制邏輯,而DJNZ的執行過(guò)程是先執行循環(huán)體后進(jìn)行循環(huán)判斷.按照DJNZ的控制流程,只有do-while語(yǔ)句符合這個(gè)條件,因此當循環(huán)次數不是常量而是變量時(shí),就必須使用do-while循環(huán)語(yǔ)句了.
綜上所述,若要使用DJNZ指令提高程序效率,在設計循環(huán)程序中應堅持以下三大原則:
① 采用遞減計數;
② 先減后判斷,減與判斷連續進(jìn)行;
③ 循環(huán)次數為變量時(shí),采用do-while循環(huán).
8051單片機有兩條循環(huán)指令,即DJNZ Rn,rel和DJNZ direct,rel.對于基本型單片機而言,兩者的執行時(shí)間都是2個(gè)機器周期,但兩者的指令長(cháng)度不同,前者占用2個(gè)字節,后者占用3個(gè)字節.循環(huán)程序還涉及到循環(huán)變量初始化操作,對于前者使用MOV Rn,#XX,2字節1周期,對于后者使用MOV direct,#XX,3字節2周期.以單層循環(huán)為例,使用工作寄存器比直接地址節省2字節1周期.除此之外,兩者相比,更重要的性能差異在于后者需要再分配一個(gè)內存單元.因為通常程序模塊都使用工作寄存器作局部變量,將工作寄存器用作循環(huán)變量不會(huì )增加內存占用量.總之,使用工作寄存器作循環(huán)計數器是設計循環(huán)程序應堅持的一項重要原則.
一般情況下,C51編譯器將循環(huán)次數賦予工作寄存器,比如
unsigned char i;
for(i=100;i;i--)
{
dosomething();
}
但是存在下述情況之一時(shí),C51編譯的結果往往令人不滿(mǎn)意:
① 函數dosomething是一個(gè)外部定義C語(yǔ)言函數;
② 函數dosomething是一個(gè)具有C語(yǔ)言接口,內部用匯編語(yǔ)言實(shí)現的,供C程序調用的外部函數.
以上兩種情況循環(huán)變量i都存放在內存單元中,即采用直接尋址方式.對于局部變量i,C51編譯器采用了直接地址存儲,其原因在于基于這種假設,即在無(wú)任何特殊處理的情況下,C51默認外部函數占用所有工作寄存器,因此在循環(huán)的外部,不能修改這些已被占用的寄存器,C51只能將循環(huán)控制變量分配在內存地址單元中.但如果循環(huán)體語(yǔ)句中僅使用少數幾個(gè)或甚至根本不使用工作寄存器,編譯器仍按這種假設處理,那么編譯器就不能顯現出它的高效性了.幸運的是,C51提供了彌補這一缺陷的偽指令REGUSE.REGUSE偽指令用于告知編譯器某函數或子程序占用了哪些寄存器或特殊功能寄存器SFR,編譯器根據函數提供的寄存器占用信息就可能將循環(huán)變量分配到循環(huán)體未占用的寄存器中,從而達到優(yōu)化設計的目的.
另外,一項開(kāi)關(guān)必須打開(kāi),即Global Register Coloring,方法是勾選Project - Options for Target - C51 - Global Register Coloring.
在情況①中,應在函數dosomething所在源程序文件中添加代碼(假設函數占用A和B):
#pragma asm
$REGUSE dosomething(A,B)
#pragma endasm
重新編譯項目后,在匯編窗口中可以看到,循環(huán)變量已使用了工作寄存器.
在情況②中,由于是匯編程序,只需增添一行代碼(也假設子程序占用A和B):
$REGUSE dosomething(A,B)
同樣可以觀(guān)察到循環(huán)變量改成了工作寄存器實(shí)現.
需要注意的是,這里所說(shuō)的寄存器占用是指在函數或子程序執行過(guò)程中可能或肯定對這些寄存器造成破壞,即執行寫(xiě)操作,對于只讀寄存器不應按占用處理.另外,參數傳遞使用的工作寄存器不必指明.
評論