今天寫程式的時候,有個地方需要 swap 某個 byte 的兩個 nibbles。其實 8051 原本就有一個 instruction 專門做這件事。若是直接寫組語的話,只要 3 行就搞定了:
MOV A,tmp ; 先將原本的值搬進 accumulator SWAP A ; swap nibbles MOV tmp,A ; 先將原本的值搬進 accumulator
問題我是用 C 在寫 code 的,所以我就開始研究是不是有辦法讓 Keil C 幫我產生這樣的 code。
首先,我們用標準的 C 的作法來做:
tmp = (tmp >> 4) | (tmp << 4);
Keil C 產生出來的組語程式如下:
MOV A,tmp SWAP A ANL A,#0F0H MOV R7,A MOV A,tmp SWAP A ANL A,#0FH ORL A,R7 MOV tmp,A
這樣翻出來的組語程式雖然看起來很長,不過我們還是可以看出 Keil C 厲害的地方:雖然我們寫的程式碼是「向左/向右 shift 4 個 bits」,但翻出來的並沒有「連續 shift 4 次」這樣的過程(8051 的 shift 一次只能 shift 1 個 bit,所以要 shift 多個 bits 就得用迴圈來跑),而是很聰明地利用 SWAP
,分別取得 high/low nibble,再重新組合成 完成的 byte。
這樣的結果讓我蠻興奮的,因為這表示 Keil C 會去看 bit operation 的位數,適時地使用處理 nibble 的指令。也就是說,我有機會找到一種寫法,讓 Keil C 翻出直接 SWAP
的組語程式。
但接下來我就卡住了,因為我找不到更好的寫法去表達 "swap nibble" 這樣的想法。
後來,我在 Keil 的文件中找到一系列 intrinsic routines。很不幸地,裡面還是沒有 swap;但卻_crol_()
和 _cror_()
這樣的 rotate 函式。於是我試著這樣寫:
tmp = _crol_(tmp,4);
結果翻出來的組語如下:
MOV R7,tmp MOV R0,#04H MOV A,R7 INC R0 SJMP ?C0081 ?C0080: RL A ?C0081: DJNZ R0,?C0080 MOV tmp,A
結果令人失望:變得更糟了!沒有直接用 SWAP
就算了,還利用迴圈的方式來處理。看樣子這個方法也行不通。
剩下一個方法:寫 inline assembly。但 Keil C 的 inline assembly 實在很難用:只要用了這個功能,該 .C 檔就不能直接編譯成 .OBJ 檔,而必須先產生出組語程式,再另外去組譯這個檔案。很麻煩。站在專案維護的角度來看,代價太高了,所以我放棄這樣的做法。
到頭來,我還是採用一開始的那種做法,畢竟在可維護性、執行速度、code size 等各方面,都算是平衡的做法。只是不能用到 8051 提供的功能,心中還是有些遺憾....
PS: 在 這個討論串中,也有人提到用 intrisic routine 的做法,而且說 Keil C 會幫忙做最佳化;不過我實驗的結果並不是這樣。
惭愧,虽然经常在用keil,但是我自己都很少去关注c to asm效率问题
现在closer大哥在用什么MCU呢??
對執行效率這麼斤斤計較算是走火入魔啊!
之前會開始鑽研這些技巧是因為有的案子走到後面 code size 不足,
一天到晚在東省西省,結果練出很多奇怪的技巧。
但站在軟體開發的角度,這些最佳化(優化)有時反而影響可讀性,
所以才說是走火入魔。
現在的工作是在寫 PC 主板上一顆叫做 Embedded Controller 的 firmware。
EC 實作沒有標準,各家廠商自由發揮。
不過基於成本、應用的考量,內核部份大多是 8051…