[問題] 為何這兩份程式碼的效能差異如此奇特
由於我是在 LLVM IR 最佳化階段發現這個問題,
跟編譯器最佳化有關,所以我發在這個版上。
這個問題困擾我很久了,想和版上的大大討論一番。
由於 LLVM IR 比較難讀,所以我把程式逆推成 C code 來增加可讀性。
先附上兩份程式碼的線上 diff:
https://www.diffchecker.com/Thbx9sDk
然後進行這樣編譯:
opt -S -passes=mem2reg more.ll -o more.ll
opt -S -passes=mem2reg less.ll -o less.ll
llc more.ll
llc less.ll
( llc 預設 O2)
得到兩份組合語言。線上 diff 如下:
https://www.diffchecker.com/NV4uuopa
(可以直接拉到左方組語 85 行 if.end 那裡)
其實可以發現,左邊那份程式碼(姑且稱之 less.c)先將 foo(rem % 5) 存起來,
只計算了 rem % 5 一次、call foo 一次;
右邊的程式碼(more.c) foo(rem % 5) 計算兩次,也就是說 rem % 5 兩次、call foo 兩次,
比左邊的程式碼多一次。
理論上,應該要比較慢才對,但我用 Linux Perf 跑過一萬次發現,
多計算 rem % 5 和 call foo 的反而比較快。
perf 中,我可以得知 instruction 數量、cycles 數量等等
instruction 數量中,less.c 比 more.c 還要少,不過這是可以預料的,
畢竟人家就是比他少做運算跟呼叫函數;
然而在 "insn per cycle" 則直接輸給了 more.c,導致實際 cycles 數量 less.c 比 more.c
還要多,
也當然執行時間比較長,但我實在是不明白原因為何。
目前的實驗做到以下:
1. llc 用 O0 最佳化 -> less.c 比 more.c 更快
-> 表示 llc 的 O2 對 more.c 那份程式碼有更好的最佳化?
但很沒道理,明明多 call 了 function 也多計算了取餘數運算,怎麼會比較快?
2. 觀察 foo(rem % 5) 的參數 "rem % 5" 值為多少,發現都是 0
-> 也就是說,多 call 的 function 都只是進入 function 直接回傳 1
-> 把該參數改成非 0 則 less.c 比 more.c 更快。
但不管如何,還是多呼叫 function 了呀,沒道理參數影響這麼多,
program counter 跳來跳去,一定會比較慢吧?
這問題雖然很實務,但真的很玄,而且困擾我很久。
我的老闆(現為碩士生)要我把原因找出來,但我找了一整天,實在是想不到原因。
希望有高手能夠幫幫我,拜託了...
--
※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 1.160.189.98 (臺灣)
※ 文章網址: https://www.ptt.cc/bbs/CompilerDev/M.1651587139.A.DF3.html
※ 編輯: shane87123 (1.160.189.98 臺灣), 05/03/2022 22:13:26
※ 編輯: shane87123 (1.160.189.98 臺灣), 05/03/2022 22:19:29
推
05/04 08:42,
2年前
, 1F
05/04 08:42, 1F
→
05/04 08:42,
2年前
, 2F
05/04 08:42, 2F
謝謝,我都忘記還有這個東西了。
用 llvm-mca 分析一下,並用 bottleneck-analysis 分析,發現 O3 的 data-dependency
比較重,研判可能跟 cse 有關:資料在程式碼初期計算好,並存放在 register 內,
之後的運算需要使用到它,就需要去存取這個 register 等等...
為了驗證這個部分,我把 cse 後的程式碼還原回去,再用 llvm-mca 跑一次分析,
確實 register-dependency 下降了、時間也降下來了。
我自己得到的結論是:O3 可以有效降低指令的執行數量,但有可能因此增加 data-depende
ncy,
會變差。
※ 編輯: shane87123 (1.160.189.98 臺灣), 05/04/2022 11:59:19
推
05/04 12:54,
2年前
, 3F
05/04 12:54, 3F
→
05/04 12:54,
2年前
, 4F
05/04 12:54, 4F
謝謝你抽空跟我一起討論。看過你的提問後,我在近一步分析。
我猜測 data dependency 應該是出在 C code 中的 a = a * call
(另一份的是 a = a * foo( rem % 5 )
我去做了 time-line 的分析
llvm-mca -timeline more.s
llvm-mca -timeline less.s
根據 llvm-mca 文件提到的:當指令在排班器的 ready 狀態的時間與在排班器的總時間相比越少的話,data dependency 越高,Latency也比較高
(https://llvm.org/docs/CommandGuide/llvm-mca.html#timeline-view)
所以我去看了那個乘法指令的狀態,大致上如下:
[1]: 在排班器裡面的時間
[2]: 在排班器且為 ready 的時間
[3]: 總共耗費的時間
less.s (我認為應該比較快但沒有比較快的)
[1] [2] [3]
90.4 0.5 36.2 imull %ebp, %eax
more.s
[1] [2] [3]
112.1 1.0 31.8 imull %r14d, %eax
我去算了一下比例:
less.s 的比例:0.5 / 90.4*100% = 0.56%
more.s 的比例:1 / 112.1 * 100% = 0.89%
表示 less.s 的乘法因 data dependency 而 latency 比較嚴重
至於 more.s 的乘法運算之前應該還有 callq foo 這個指令,應該會很慢才對,
但 llvm-mca 有提到,面對 call function 準確度不高,加上我原文提及,more.c 比較快
的原因是因為傳入 foo 的參數為0,會
直接 return 1,所以我覺得 call foo 實際執行時間應該很短
換成傳入2以上的話應該就會比較慢了
※ 編輯: shane87123 (49.216.29.37 臺灣), 05/04/2022 14:43:08
※ 編輯: shane87123 (49.216.29.37 臺灣), 05/04/2022 14:44:32
※ 編輯: shane87123 (49.216.29.37 臺灣), 05/04/2022 14:45:54
※ 編輯: shane87123 (49.216.29.37 臺灣), 05/04/2022 14:46:48
※ 編輯: shane87123 (49.216.29.37 臺灣), 05/04/2022 14:49:12
推
05/04 17:47,
2年前
, 5F
05/04 17:47, 5F
推
05/04 23:06,
2年前
, 6F
05/04 23:06, 6F
→
05/04 23:06,
2年前
, 7F
05/04 23:06, 7F
※ 編輯: shane87123 (1.160.189.98 臺灣), 05/05/2022 01:22:46
→
05/05 06:06,
2年前
, 8F
05/05 06:06, 8F
→
05/05 06:06,
2年前
, 9F
05/05 06:06, 9F
謝謝你提醒我ILP,我昨天看文章沒看到這塊
所以我後來有做另一個實驗
就是我把兩份程式碼用單一核心去跑
發現less比more快了
→
05/05 06:13,
2年前
, 10F
05/05 06:13, 10F
→
05/05 06:13,
2年前
, 11F
05/05 06:13, 11F
→
05/05 06:13,
2年前
, 12F
05/05 06:13, 12F
→
05/05 06:13,
2年前
, 13F
05/05 06:13, 13F
我認為的因果關係是這樣:
1. less比more多data dependency導致ILP更差
2. ILP變差,導致在多核心運算時,less的IPC比more還低
3. 多核運算中,速度上 less 比more 慢
4. 單一核心運算中,more失去了ILP比較好的優勢,而less指令比較少的優勢還在,所以單
核運算less比more快
雖然比較IPC好像真的不合適,因為他應該是從cycles和指令數量去逆推IPC
但我想不到更好的因果關係去說明這件事情OTZ
※ 編輯: shane87123 (101.12.17.166 臺灣), 05/05/2022 12:36:56
※ 編輯: shane87123 (101.12.17.166 臺灣), 05/05/2022 12:40:10
推
05/15 05:38,
2年前
, 14F
05/15 05:38, 14F
→
05/15 16:20,
2年前
, 15F
05/15 16:20, 15F
→
05/15 16:20,
2年前
, 16F
05/15 16:20, 16F
→
05/15 16:22,
2年前
, 17F
05/15 16:22, 17F
討論串 (同標題文章)
以下文章回應了本文:
完整討論串 (本文為第 1 之 2 篇):