Visual C++ >> Console
(y*3.14)、((y*314)/100)、((y*201)>>6)
這是一個相當有趣的選擇題,因為從使用者選擇的答案就可以知道使用者對於程式效能的要求有多高,雖然電腦硬體不斷進化提升中,但是寫程式不單單是寫在電腦上執行,也需要考慮到單晶片、嵌入式系統上面,而要寫出高效能的程式,基本功是不可缺少的技能。
一般C/C++程式設計師對於除法、乘法的細節都不會有過多的注意,因為有很多的Bug需要解決,然而,解決完Bug之後,是否會回頭去解決程式效能的問題呢?答案是否定的,因為怕動了程式之後問題更多,因此這個問題就這樣被略過,除非這個效能提升是一個關鍵問題,否則這個問題可能不太有人去理會,因此,一開始就把事情做對,是有其必要性,寧願一開始就多注意一些細節,也不要後來再來大改程式。
原始程式如下(main.c)
#include <stdio.h> volatile unsigned int result=0; int main(void) { unsigned int y; y*= 3.14; result = y; return 0; }
這個程式相當簡單,就只有做浮點數的乘法,但是,是否有更好的寫法呢?答案是肯定有的,有寫過組合語言、提升效能程式的工程師就會有更多改善的想法,司徒就大約說明一下過程,首先,浮點數一般使用IEEE 754的做法,使用整數去表示浮點數,然而,它的運算量是相當大的,一般單晶片或嵌入式編譯系統都會調用系統函數去運算,所以消耗的時間就會大大增加。
Keil C 8051組合語言結果(main.c)
6: int main(void) 7: { 8: unsigned int y; 9: 10: y*= 3.14; C:0x0262 AC0A MOV R4,0x0A C:0x0264 AD0B MOV R5,0x0B C:0x0266 E4 CLR A C:0x0267 12010E LCALL C?FCASTI(C:010E) C:0x026A 7BC3 MOV R3,#0xC3 C:0x026C 7AF5 MOV R2,#0xF5 C:0x026E 7948 MOV R1,#0x48 C:0x0270 7840 MOV R0,#0x40 C:0x0272 120003 LCALL C?FPMUL(C:0003) C:0x0275 120147 LCALL C?CASTF(C:0147) C:0x0278 8E0A MOV 0x0A,R6 C:0x027A 8F0B MOV 0x0B,R7 11: result = y; C:0x027C 850A08 MOV result(0x08),0x0A C:0x027F 850B09 MOV 0x09,0x0B 12: return 0; C:0x0282 E4 CLR A C:0x0283 FE MOV R6,A C:0x0284 FF MOV R7,A 13: }
編譯成組合語言後可以發現呼叫相當多的系統函數(如:C?FPMUL),呼叫函數本來就會耗費資源,更何況運算內容。
Keil C ARM組合語言結果(main.c 最佳化Level-0)
6: int main(void) 7: { 8: unsigned int y; 9: 0x000001A0 B510 PUSH {r4,lr} 10: y*= 3.14; 0x000001A2 F000F89F BL.W _dfltu (0x000002E4) 0x000001A6 A205 ADR r2,{pc}+2 ; @0x000001BC 0x000001A8 6853 LDR r3,[r2,#0x04] 0x000001AA 6812 LDR r2,[r2,#0x00] 0x000001AC F000F8BE BL.W _dmul (0x0000032C) 0x000001B0 F000F85E BL.W _dfixu (0x00000270) 11: result = y; 0x000001B4 4903 LDR r1,[pc,#12] ; @0x000001C4 0x000001B6 6008 STR r0,[r1,#0x00] 12: return 0; 0x000001B8 2000 MOVS r0,#0x00 13: }
不開最佳化的情況下還是一樣呼叫系統函數做浮點運算。
知道浮點數運算相當耗時之後,一般工程師會從整數下手,也就是改成如下程式
#include <stdio.h> volatile unsigned int result=0; int main(void) { unsigned int y; y = ((y * 314) / 100); result = y; return 0; }
改成如上程式,已經算是比較快速的做法,也很適合大多數系統的提升準則。
那是否還有更好的做法呢?答案是有的,也就是做改成位移方式,如果是基於2的整數倍除法可以使用邏輯右移運算,而基於2的整數倍乘法可以使用邏輯左移運算,因此,可以再度改成更佳的運算方式
#include <stdio.h> volatile unsigned int result=0; int main(void) { unsigned int y; y = ((y * 201) >> 6); result = y; return 0; }
當然這樣的改法需要去拆解,如果遇到不能拆解時,就只能使用整數代替除法的方式。
使用者可能會提出質疑,問說編譯器已經有最佳化選項,只要一打開就可以了ㄚ,為何要做到這麼複雜呢?司徒必須說,編譯器一般尚無法做到非常聰明的安排,像上面的例子,編譯器一般無法拆解,所以編譯成組合語言,還是會帶較大的數值運算,當然也有例外的編譯器,但是,重點是你是否會去想如何改進程式效能,以及你的解法思路是怎麼思考的呢?
Keil C ARM(最大最佳化效果)
6: int main(void) 7: { 8: unsigned int y; 9: 0x000001A0 B510 PUSH {r4,lr} 10: y*= 3.14; 0x000001A2 F000F89F BL.W _dfltu (0x000002E4) 0x000001A6 A205 ADR r2,{pc}+2 ; @0x000001BC 0x000001A8 CA0C LDM r2,{r2-r3} 0x000001AA F000F8BF BL.W _dmul (0x0000032C) 0x000001AE F000F85F BL.W _dfixu (0x00000270) 11: result = y; 0x000001B2 4904 LDR r1,[pc,#16] ; @0x000001C4 0x000001B4 6008 STR r0,[r1,#0x00] 12: return 0; 0x000001B6 2000 MOVS r0,#0x00 13: }