Linux kernel - rtw88-usb : gcc optimization / c coding style analysis
收錄在 AIoT Ameba 2020 - Just for Fun
--
周一有點懶懶的, 剛好上周有段 code, 心中有個疑問, 來做個實驗看看.
上週在 rtw88-usb refine checksum function,
https://github.com/neojou/rtw88-usb/commit/321ea93df682e45dcffcdb00c2cdae381fdd7a64
看到底下這段 code
https://github.com/ulli-kroll/rtl8822bu/blob/master/hal/halmac/halmac_88xx/halmac_api_88xx.c
checksum 是 2 byte == 16 bit,
這是把 資料, 每 2 byte (16 bit) 依序取出來, 做 xor.
乍看之下沒甚麼問題, 迴圈次數可以少一半,
但這樣寫是否好呢?
==
這問題我們可以簡化成:
有底下兩個 function
chksum1() 和 chksum2()
哪種寫法比較好?
chksum1(data, len/2) v.s. chksum2(data, len)
https://github.com/neojou/new_ldd/blob/master/gcc-optimization/func.c
==
程式可以從幾個面向來看.
1. 易讀性:
最近剛好看到一篇: 如何提升你程式可讀性的技巧
chksum1() 的
chksum ^= *(data + 2 * i) ^ *(data + 2 * i + 1);
checksum ^= *data++
應該是 chksum2() 比較容易懂
2. 程式執行效率 和 code size.
這邊剛好也和 compiler optimization 有關
例如 gcc 的 optimization
https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html
常見的有
O2: 盡量加強 效能/ 減少 code size
O3: 更加強效能
Os: O2 的幾乎都有, 主要 O2 選項中,
若加強效能下會增加 code size 的話則不採用.
如對 編譯器 的優化 有興趣的話, 底下這篇共筆很值得一看
https://hackmd.io/@sysprog/c-compiler-optimization?type=view
我們用 objdump -S 來看看編譯出來的組合語言.
另外因為 x86 是 CISC 指令集, 每個指令所需要花費執行的 cycle 不同,
我們也改實驗用 ARM RISC 指令集來編譯看看.
https://github.com/neojou/new_ldd/blob/master/gcc-optimization/O2.txt
(1) loop 寫法
用 for 迴圈方式:
用 while 方式:
chksum ^= *data++;
74: e0d020b2 ldrh r2, [r0], #2
80: e0211002 eor r1, r1, r2
這邊可以發現, 在 O2 比較時, 倒還好, 只是單純 ldrh / eor 兩次或一次的差別.
但如在 for loop 所提, 多的這行
add r3, r3, #4
這會降低效能, 增加 code size
.
反觀 chksum2(): 它的指標移動, 在 74行 和 ldrh 一起做了..
更大的問題會在 我們 用 O3 compile 看的出來.
https://github.com/neojou/new_ldd/blob/master/gcc-optimization/O3.txt
用 O3 編譯, 可以發現 chksum1() 和 O2 時的 code 差不多,
但 chksum2() 可以用到 compiler loop unrolling 的技巧 展開來加速
( loop unrolling 在上方共筆那篇也有提到. )
換言之, chksum1() 的寫法, compiler 其實和人一樣, 被搞混了, 沒能看懂,
但 chksum2() 的寫法, compiler 知道, 這就只是單純把資料每 16 bit 取出來做 xor.
所以有辦法用 loop unrolling 技巧來加速.
結論:
compiler 技術日新月異, gcc 今年也到 10 了, 所以只要多留意寫法, 增加可讀性,
gcc 有 warning 時, 要積極地去除 warning. 不用再自己 "搞剛" 用自己以為的方式去加速.
反造成 compiler 的誤解.
PS: 德國這位仁兄上週 email 聯繫上了, 他也很樂意來開發 rtw88-usb, 加速發展
https://github.com/ulli-kroll/rtw88-usb
也歡迎有興趣的朋友, 一起來開發. ^^
--
周一有點懶懶的, 剛好上周有段 code, 心中有個疑問, 來做個實驗看看.
上週在 rtw88-usb refine checksum function,
https://github.com/neojou/rtw88-usb/commit/321ea93df682e45dcffcdb00c2cdae381fdd7a64
看到底下這段 code
https://github.com/ulli-kroll/rtl8822bu/blob/master/hal/halmac/halmac_88xx/halmac_api_88xx.c
checksum 是 2 byte == 16 bit,
這是把 資料, 每 2 byte (16 bit) 依序取出來, 做 xor.
乍看之下沒甚麼問題, 迴圈次數可以少一半,
但這樣寫是否好呢?
==
這問題我們可以簡化成:
有底下兩個 function
chksum1() 和 chksum2()
哪種寫法比較好?
chksum1(data, len/2) v.s. chksum2(data, len)
https://github.com/neojou/new_ldd/blob/master/gcc-optimization/func.c
==
程式可以從幾個面向來看.
1. 易讀性:
最近剛好看到一篇: 如何提升你程式可讀性的技巧
chksum1() 的
chksum ^= *(data + 2 * i) ^ *(data + 2 * i + 1);
和 chksum2() 比較:
checksum ^= *data++
應該是 chksum2() 比較容易懂
2. 程式執行效率 和 code size.
這邊剛好也和 compiler optimization 有關
例如 gcc 的 optimization
https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html
常見的有
O2: 盡量加強 效能/ 減少 code size
O3: 更加強效能
Os: O2 的幾乎都有, 主要 O2 選項中,
若加強效能下會增加 code size 的話則不採用.
如對 編譯器 的優化 有興趣的話, 底下這篇共筆很值得一看
https://hackmd.io/@sysprog/c-compiler-optimization?type=view
我們用 objdump -S 來看看編譯出來的組合語言.
另外因為 x86 是 CISC 指令集, 每個指令所需要花費執行的 cycle 不同,
我們也改實驗用 ARM RISC 指令集來編譯看看.
https://github.com/neojou/new_ldd/blob/master/gcc-optimization/O2.txt
(1) loop 寫法
用 for 迴圈方式:
for (i = 0; i < len; i++) 14: e3510000 cmp r1, #0 18: da00000a ble 48 <chksum1+0x48>
1c: e1a03000 mov r3, r0 20: e3a00000 mov r0, #0 24: e0831101 add r1, r3, r1, lsl #2
....
2c: e2833004 add r3, r3, #4
for (i = 0; i < len; i++)
34: e1510003 cmp r1, r3
....
for (i = 0; i < len; i++) 40: 1afffff8 bne 28 <chksum1+0x28> 44: e89da800 ldm sp, {fp, sp, pc}
這邊 r1 放 len, r3 是 i, 每次移動一個 DWORD (4 byte) 位置計算, 所以是 加 4
用 while 方式:
while (len--) 64: e3510000 cmp r1, #0 68: e2413001 sub r3, r1, #1 6c: 0a000005 beq 88 <chksum2+0x38> 70: e3a01000 mov r1, #0
...
while (len--) 78: e2433001 sub r3, r3, #1 7c: e3730001 cmn r3, #1...
while (len--) 84: 1afffffa bne 74 <chksum2+0x24>
可以發現 while 只要一個變數 len, 和 0 做比較.
寫法會比 for loop 多一個 i 變數的方式, 來的單純.
寫法會比 for loop 多一個 i 變數的方式, 來的單純.
所以在 Linux kernel, 能用 while (len--) 這寫法的話,
都會盡量採用這方式, 來取代 for loop 的寫法.
(2) checksum 計算寫法:
chksum1():
chksum ^= *(data + 2 * i) ^ *(data + 2 * i + 1);
28: e1d320b0 ldrh r2, [r3]
2c: e2833004 add r3, r3, #4
30: e153c0b2 ldrh ip, [r3, #-2]
38: e022200c eor r2, r2, ip
3c: e0200002 eor r0, r0, r2
chksum2():chksum ^= *data++;
74: e0d020b2 ldrh r2, [r0], #2
80: e0211002 eor r1, r1, r2
這邊可以發現, 在 O2 比較時, 倒還好, 只是單純 ldrh / eor 兩次或一次的差別.
但如在 for loop 所提, 多的這行
add r3, r3, #4
這會降低效能, 增加 code size
.
反觀 chksum2(): 它的指標移動, 在 74行 和 ldrh 一起做了..
更大的問題會在 我們 用 O3 compile 看的出來.
https://github.com/neojou/new_ldd/blob/master/gcc-optimization/O3.txt
用 O3 編譯, 可以發現 chksum1() 和 O2 時的 code 差不多,
但 chksum2() 可以用到 compiler loop unrolling 的技巧 展開來加速
( loop unrolling 在上方共筆那篇也有提到. )
換言之, chksum1() 的寫法, compiler 其實和人一樣, 被搞混了, 沒能看懂,
但 chksum2() 的寫法, compiler 知道, 這就只是單純把資料每 16 bit 取出來做 xor.
所以有辦法用 loop unrolling 技巧來加速.
結論:
compiler 技術日新月異, gcc 今年也到 10 了, 所以只要多留意寫法, 增加可讀性,
gcc 有 warning 時, 要積極地去除 warning. 不用再自己 "搞剛" 用自己以為的方式去加速.
反造成 compiler 的誤解.
PS: 德國這位仁兄上週 email 聯繫上了, 他也很樂意來開發 rtw88-usb, 加速發展
https://github.com/ulli-kroll/rtw88-usb
也歡迎有興趣的朋友, 一起來開發. ^^
留言
張貼留言