測試多線程對多核cpu的分支預測的影響
前言:
現代的cpu都有流水線,分支預測功能,CPU的分支預測準確性可以達到98%以上,但是如果預測失敗,則流水線失效,性能損失很嚴重。
CPU使用的分支預測技術可以參考:
正確地利用這些特性,可以寫出高效的程序。
比如在寫if,else語句時,應當把大概率事件放到if語句中,把小概率事件放到else語句中。
但是通常這種考慮都是基於單線程的,在多線程下有可能出現意外情況,比如多個線程同時執行同一處的代碼。
測試:
下麵基於Intel Core i5的一些多線程分支預測的測試。
測試思路(真實測試時發現不止以下三種情況,詳細見下麵的測試結果):
兩個線程執行同一處代碼,而if判斷總為true。
兩個線程執行同一處代碼,一個的if判斷總為true,另一個的if判斷時為true,時為false。
兩個線程執行不同的代碼(邏輯功能一樣,隻是位置不同),一個的if判斷總為true,另一個的if判斷時為true,時為false。
代碼如下:
其中test1測試的是同一處代碼,當傳入偶數參數時,則if判斷總為true,當傳入奇數參數時,if判斷時為true時為false。
test2函數測試的是不同一處的代碼,當傳入偶數參數時,則if判斷總為true,當傳入奇數參數時,if判斷時為true時為false。
import java.util.concurrent.CountDownLatch; public class Test { public static int loop = 1000000000; public static int sum = 0; public static CountDownLatch startGate; public static CountDownLatch endGate; public static void test1(int x1, int x2) throws InterruptedException{ startGate = new CountDownLatch(1); endGate = new CountDownLatch(2); new Thread(new T1(x1)).start(); new Thread(new T1(x2)).start(); Test.startGate.countDown(); Test.endGate.await(); } public static void test2(int x1, int x2) throws InterruptedException{ startGate = new CountDownLatch(1); endGate = new CountDownLatch(2); new Thread(new T1(x1)).start(); new Thread(new T2(x2)).start(); Test.startGate.countDown(); Test.endGate.await(); } } class T1 implements Runnable{ int xxx = 0; public T1(int xxx){ this.xxx = xxx; } @Override public void run() { try { int sum = 0; int temp = 0; Test.startGate.await(); long start = System.nanoTime(); for(int i = 0; i < Test.loop; ++i){ temp += xxx; if(temp % 2 == 0){ sum += 100; }else{ sum += 200; } } Test.sum += sum; long end = System.nanoTime(); System.out.format("%s, T1(%d): %d\n", Thread.currentThread().getName(), xxx, end - start); } catch (InterruptedException e) { e.printStackTrace(); }finally{ Test.endGate.countDown(); } } } class T2 implements Runnable{ int xxx = 0; public T2(int xxx){ this.xxx = xxx; } @Override public void run() { try { int sum = 0; int temp = 0; Test.startGate.await(); long start = System.nanoTime(); for(int i = 0; i < Test.loop; ++i){ temp += xxx; if(temp % 2 == 0){ sum += 100; }else{ sum += 200; } } Test.sum += sum; long end = System.nanoTime(); System.out.format("%s, T2(%d): %d\n", Thread.currentThread().getName(), xxx, end - start); } catch (InterruptedException e) { e.printStackTrace(); }finally{ Test.endGate.countDown(); } } }
因為測試的情況多種,簡潔表述如下:
一個test1函數會有兩個結果,如test1(2, 3)得到兩個結果2.1s,2.2s,表示兩個線程執行同一份代碼,一個的if判斷總是true(2是偶數),另一個的總是false(3是奇數),其中第一個線程平均執行一次計算的時間是2.1s,第二個線程平均執行一次計算的時間是2.2s。
測試的main函數中有兩個for循環,每個10次。在表述測試結果時,用簡略表示,如:
一行數據表示執行一次main函數的結果,後麵的時間是粗略平均計算得到的
test1(2,3) | 2.1s | 2.1s | test1(2,4) | 2.1s | 2.1s |
main函數:
public static void main(String[] args) throws InterruptedException { for(int i = 0; i < 10; ++i){ test1(2, 3); } System.out.println("!!!!!!!!!!!!!!!!!!!!"); for(int i = 0; i < 10; ++i){ test1(2, 4); } }
測試結果如下:
1 | test1(2,3) | 2.0s | 2.0s | test1(2,4) | 1.8s | 2.0s |
2 | test1(2,4) | 1.3s | 1.3s | test1(2,3) | 1.3s | 1.8s |
3 | ||||||
4 | test2(2,3) | 1.3s | 1.7s | test2(2,4) | 1.3s | 1.9s |
5 | test2(2,4) | 1.3s | 1.3s | test2(2,3) | 1.3s | 1.8s |
先來分析第1行數據,test1(2,3)的結果是最壞的,顯然這是因為兩個線程執行同一份代碼,且兩者的分支預測結果互相幹擾,導致總是不準。
但是為什麼後麵的test1(2,4)的結果也是比較差?盡管兩個線程執行的是同一份代碼,但是兩個線程中if的判斷都總是true,為什麼會出現耗時比較多的情況?
簡單推測1:可能是之前的test1(2,3)影響了test1(2,4)的分支預測的結果,分支預測器中有曆史表,前麵執行的分支預測曆史會影響後麵的選擇。
再來分析第2行,顯然test1(2,4)是最好的結果,兩個線程執行同一份代碼,且if總是為true。再看第2行的test(2,3)結果,比第1行的要好,為什麼這個和第1行的數據差這麼多?
簡單推測2:應該是不同的核有自己的分支預測器。
再來看test2函數的測試結果,從第4行來看,test2(2,3)的結果符合預期,但是顯然test2(2,4)的結果不理想,為什麼兩個線程執行不同地方的代碼,if判斷總是true,結果會不同?
簡單推測3:受test(2,3)的結果的影響,結合簡單推測1和2,就可以理解為什麼test(2,4)的前一個結果是1.3s,後一個是1.9s了。
再來分析第5行數據,這行數據完全符合預期。
總結:
推測:i5的分支預測器是一個混合的分支預測器。每個核都有曆史表,每一個核都有自己的分支預測器。
多線程下的分支預測並不是很樂觀,如果可以避開多個線程執行同一份代碼,且分支預測條件的結果總是變化,則盡量避開。
最後更新:2017-04-02 16:47:46