1. 程式人生 > >理解CPU分支預測,提高代碼效率

理解CPU分支預測,提高代碼效率

href ann roc 討論 erro 用戶體驗 工作效率 現在 tde

摘要: 技術傳播的價值,不僅僅體現在通過商業化產品和開源項目來縮短我們構建應用的路徑,加速業務的上線速率,也會體現在優秀程序員在工作效率提升、產品性能優化和用戶體驗改善等小技巧方面的分享,以提高我們的工作能力。

技術分享圖片

技術傳播的價值,不僅僅體現在通過商業化產品和開源項目來縮短我們構建應用的路徑,加速業務的上線速率,也會體現在優秀程序員在工作效率提升、產品性能優化和用戶體驗改善等小技巧方面的分享,以提高我們的工作能力。

從本期開始,我們將邀請來自阿裏巴巴各個技術團隊的程序員,涵蓋中間件、前端、移動開發、大數據和人工智能等多個技術領域,分享他們在工作中的小技巧, 內容力求簡短、實用和可操作。

第一期的分享嘉賓,是來自阿裏巴巴中間件技術團隊的程序員 - 斷嶺,他是阿裏微服務開源項目 Dubbo 的項目組成員,也是Java線上診斷開源項目 Arthas 的負責人。

第一期:理解CPU分支預測,提高代碼效率

一、基礎概念:
Dubbo: 是一款高性能、輕量級的開源Java RPC框架,提供了三大核心能力:面向接口的遠程方法調用,智能容錯和負載均衡,以及服務自動註冊和發現;
ChannelEventRunnable: Dubbo 裏所有網絡事件的回調接口;
JMH:即Java Microbenchmark Harness,是專門用於代碼微基準測試的工具套件。在性能優化的過程中,可以使用JMH對優化的結果進行量化的分析。
二、需求緣起:
在Stack Overflow上有一個非常著名的問題:為什麽處理有序數組要比非有序數組快?從問題的結論來看,是分支預測對代碼運行效率的提升起到了非常重要的作用。

現今的CPU是都支持分支預測(branch prediction)和指令流水線(instruction pipeline),這倆的結合可以極大的提高CPU的工作效率,從而提高代碼執行效率。但這僅適用於簡單的if跳轉,但對於Switch跳轉,CPU則沒有太好的解決辦法,因為Switch本質上是據索引,是從地址數組裏取地址再跳轉。

三、思考和方案假設:
要提高代碼執行效率,一個重要的實現原則就是盡量避免CPU把流水線清空,從Stack Overflow上的討論結果來看,通過提高分支預測的成功率,是可以降低CPU對流水線清空的概率。那麽,除了在硬件層面,是否可以考慮代碼層面幫CPU把判斷提前,來提高代碼執行效率呢?

四、方案驗證:
在Dubbo的ChannelEventRunnable裏有一個Switch來判斷channel state。當一個channel建立起來之後,超過99.9%的情況,它的state都是ChannelState.RECEIVED,我們可以考慮,把這個判斷提前。

以下通過JMH來驗證,把判斷提前後是否就可以提高代碼執行效率。

率。

public class TestBenchMarks {
public enum ChannelState {
CONNECTED, DISCONNECTED, SENT, RECEIVED, CAUGHT }

@State(Scope.Benchmark)
public static class ExecutionPlan {
@Param({ "1000000" })
public int size;
public ChannelState[] states = null;

@Setup
public void setUp() {
    ChannelState[] values = ChannelState.values();
    states = new ChannelState[size];
    Random random = new Random(new Date().getTime());
    for (int i = 0; i < size; i++) {
        int nextInt = random.nextInt(1000000);
        if (nextInt > 100) {
            states[i] = ChannelState.RECEIVED;
        } else {
            states[i] = values[nextInt % values.length];
        }
    }
}

}

@Fork(value = 5)@Benchmark
br/>@Benchmark
public void benchSiwtch(ExecutionPlan plan, Blackhole bh) {
int result = 0;
for (int i = 0; i < plan.size; ++i) {
switch (plan.states[i]) {
case CONNECTED:
result += ChannelState.CONNECTED.ordinal();
break;
case DISCONNECTED:
result += ChannelState.DISCONNECTED.ordinal();
break;
case SENT:
result += ChannelState.SENT.ordinal();
break;
case RECEIVED:
result += ChannelState.RECEIVED.ordinal();
break;
case CAUGHT:
result += ChannelState.CAUGHT.ordinal();
break;
}
}
bh.consume(result);
}

@Fork(value = 5)@Benchmark
br/>@Benchmark
public void benchIfAndSwitch(ExecutionPlan plan, Blackhole bh) {
int result = 0;
for (int i = 0; i < plan.size; ++i) {
ChannelState state = plan.states[i];
if (state == ChannelState.RECEIVED) {
result += ChannelState.RECEIVED.ordinal();
} else {
switch (state) {
case CONNECTED:
result += ChannelState.CONNECTED.ordinal();
break;
case SENT:
result += ChannelState.SENT.ordinal();
break;
case DISCONNECTED:
result += ChannelState.DISCONNECTED.ordinal();
break;
case CAUGHT:
result += ChannelState.CAUGHT.ordinal();
break;
}
}
}
bh.consume(result);
}}
驗證說明:

benchSiwtch裏是純Switch判斷
benchIfAndSwitch 裏用一個if提前判斷state是否ChannelState.RECEIVED
Benchmark結果是:

Result "io.github.hengyunabc.jmh.TestBenchMarks.benchSiwtch":
576.745 ±(99.9%) 6.806 ops/s [Average]
(min, avg, max) = (490.348, 576.745, 618.360), stdev = 20.066
CI (99.9%): [569.939, 583.550](assumes normal distribution)
Run complete. Total time: 00:06:48

Benchmark (size) Mode Cnt Score Error Units
TestBenchMarks.benchIfAndSwitch 1000000 thrpt 100 1535.867 ± 61.212 ops/s
TestBenchMarks.benchSiwtch 1000000 thrpt 100 576.745 ± 6.806 ops/s
可以看到,提前if判斷提高了近3倍的代碼效率,這種技巧可以放在性能要求嚴格的地方。

五、總結:
Switch對於CPU來說難以做分支預測;
某些Switch條件如果概率比較高,可以在代碼層設置提前if判斷,充分利用CPU的分支預測機制;

理解CPU分支預測,提高代碼效率