經典問題之「分支預測」

清塵閒聊發表於2019-03-22

問題

來源 :stackoverflow
為什麼下面程式碼排序後累加比不排序快?

public static void main(String[] args) {
        // Generate data
        int arraySize = 32768;
        int data[] = new int[arraySize];

        Random rnd = new Random(0);
        for (int c = 0; c < arraySize; ++c)
            data[c] = rnd.nextInt() % 256;

        // !!! With this, the next loop runs faster
        Arrays.sort(data);

        // Test
        long start = System.nanoTime();
        long sum = 0;

        for (int i = 0; i < 100000; ++i)
        {
            // Primary loop
            for (int c = 0; c < arraySize; ++c)
            {
                if (data[c] >= 128)
                    sum += data[c];
            }
        }

        System.out.println((System.nanoTime() - start) / 1000000000.0);
        System.out.println("sum = " + sum);
    }
複製程式碼

在我電腦上沒有排序耗時:10.78390589
排序後耗時:4.552145206

出現上面這個時長差異的罪魁禍首就是這段程式碼 :

if (data[c] >= 128)
    sum += data[c];
複製程式碼

排序後資料的示例:

T = 表示進入分支
N = 表示未進入分支

data[] = 0, 1, 2, 3, 4, ... 126, 127, 128, 129, 130, ... 250, 251, 252, ...
branch = N  N  N  N  N  ...   N    N    T    T    T  ...   T    T    T  ...

 = NNNNNNNNNNNN ... NNNNNNNTTTTTTTTT ... TTTTTTTTTT  (容易去預測)
複製程式碼

沒有排序資料的示例:

data[] = 226, 185, 125, 158, 198, 144, 217, 79, 202, 118,  14, 150, 177, 182, 133, ...
branch =   T,   T,   N,   T,   T,   T,   T,  N,   T,   N,   N,   T,   T,   T,   N  ...

  = TTNTTTTNTNNTTTN ...   (全是隨機資料 - 很難去預測)
複製程式碼

假如我們把程式碼裡條件判斷換成下面程式碼:

int t = (data[c] - 128) >> 31;
sum += ~t & data[c];
複製程式碼

沒有排序耗時:2.698193263
排序後耗時 :2.753661927
說明沒有用到條件判斷語句沒有排序和排好序的耗時很相近。
在現代處理器中,都引入了分支預測來提高指令流水線的效能。所以就導致排序後比沒有排序快。

分支預測

條件分支指令通常具有兩路後續執行分支。即不採取(not taken)跳轉,順序執行後面緊挨JMP的指令;以及採取(taken)跳轉到另一塊程式記憶體去執行那裡的指令。
是否需要跳轉,只有到真正執行時才能確定。如果沒有分支預測器,處理器將會等待分支指令通過了指令流水線的執行階段,才把下一條指令送入流水線的第一個階段—取指令階段(fetch stage),這種技術叫做 流水線停頓
分支預測器就是猜測條件判斷會走哪一路,如果猜對,就避免流水線停頓造成的時間浪費。如果猜錯,那麼流水線中推測執行的那些中間結果全部放棄,重新獲取正確的分支路線上的指令開始執行,這導致了程式執行的延遲。

相關文章