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