我有一個簡單的代碼,它對陣列中的元素求和并回傳它們:
// Called with jump == 0
int performance(int jump, int *array, int size) {
int currentIndex = 0;
int total = 0;
// For i in 1...500_000_000
for (int i = 0; i < 500000000; i ) {
currentIndex = (currentIndex jump) % size;
total = array[currentIndex];
}
return total;
}
我注意到一個怪異的行為:存在% size有一個非常大的性能影響(?10倍速度較慢)甚至壽jump是0因此被不斷訪問相同的陣列元素(0)。只需洗掉即可% size大大提高性能。
我會認為這只是產生這種差異的模計算,但現在說我用total = array[currentIndex] % size;(因此也計算模)替換了我的總和線,性能差異幾乎不明顯。
我正在 arm64 機器上用 -O3 和 clang 編譯它。
什么可能導致這種情況?
uj5u.com熱心網友回復:
sdiv msub延遲約為 10 倍add延遲聽起來很正常。
即使行內了一個size不是 2 的冪的編譯時常量,這仍然是一個乘法逆和一個msub(乘減)來獲得余數,所以一個至少有兩個乘法和一個移位的 dep 鏈。
也許在關鍵路徑上有一些額外的指令,用于具有恒定大小(即使是正數)的有符號余數,因為陣列也是 signed int。例如-4 % 3必須-1在 C 中生產。
看
- 每條匯編指令需要多少個 CPU 周期?
- 預測現代超標量處理器上的操作的延遲需要考慮哪些因素,我如何手動計算它們?
說我用
total = array[currentIndex] % size;(因此也計算了模數)替換了我的總和線
其余部分不是回圈攜帶的依賴鏈的一部分。( https://fgiesen.wordpress.com/2018/03/05/a-whirlwind-introduction-to-dataflow-graphs/ )
多個余數計算可以并行進行,因為下一個array[idx]加載地址僅取決于 = jump添加指令。
如果您在吞吐量限制方面沒有瓶頸,那么這些剩余結果可能會以 1/clock 的吞吐量準備就緒,并且 OoO exec 在迭代之間重疊 dep 鏈。唯一的延遲瓶頸是回圈計數器/索引 和total = ...,它們都是add具有 1 個周期延遲的整數。
所以實際上,瓶頸很可能是吞吐量(整個回圈體的),而不是那些延遲瓶頸,除非你在一個非常寬的 CPU 上進行測驗,每個周期都可以完成很多作業。(令人驚訝的是,您根本不會因為引入而減慢速度%。除非total在不使用結果的情況下行內后得到優化。)
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/331918.html
下一篇:printf(str)和fwrite(str,1,strlen(str),stdout)之間有什么明顯的區別嗎?
