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

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

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

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

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

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

1、基礎概念:

  1. Dubbo: 是一款高性能、輕量級的開源Java RPC框架,提供了三大核心能力:面向接口的遠程方法調用,智能容錯和負載均衡,以及服務自動註冊和發現;
  2. ChannelEventRunnable: Dubbo 裏全部網絡事件的回調接口;
  3. JMH:即Java Microbenchmark Harness,是專門用於代碼微基準測試的工具套件。在性能優化的過程當中,可使用JMH對優化的結果進行量化的分析。

2、需求緣起:

在Stack Overflow上有一個很是著名的問題:爲何處理有序數組要比非有序數組快?從問題的結論來看,是分支預測對代碼運行效率的提高起到了很是重要的做用。性能優化

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

3、思考和方案假設:

要提升代碼執行效率,一個重要的實現原則就是儘可能避免CPU把流水線清空,從Stack Overflow上的討論結果來看,經過提升分支預測的成功率,是能夠下降CPU對流水線清空的機率。那麼,除了在硬件層面,是否能夠考慮代碼層面幫CPU把判斷提早,來提升代碼執行效率呢?負載均衡

4、方案驗證:

在Dubbo的ChannelEventRunnable裏有一個Switch來判斷channel state。當一個channel創建起來以後,超過99.9%的狀況,它的state都是ChannelState.RECEIVED,咱們能夠考慮,把這個判斷提早。框架

如下經過JMH來驗證,把判斷提早後是否就能夠提升代碼執行效率。dom

率。

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
@BenchmarkMode(Mode.Throughput)
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
@BenchmarkMode(Mode.Throughput)
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倍的代碼效率,這種技巧能夠放在性能要求嚴格的地方。

5、總結:

  • Switch對於CPU來講難以作分支預測;
  • 某些Switch條件若是機率比較高,能夠在代碼層設置提早if判斷,充分利用CPU的分支預測機制;

原文連接 

相關文章
相關標籤/搜索