分支预测ä¸èµ·ä½œç”¨å—?

时间:2014-01-29 13:23:05

标签: java performance microbenchmark branch-prediction jmh

在æ到this问题时,答案指出未排åºçš„数组花费更多时间,因为它未通过分支预测测试。但是如果我们对程åºè¿›è¡Œå¾®å°çš„改动:

import java.util.Arrays;
import java.util.Random;


public class Main{

    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);
    }
}

这里我已ç»æ›¿æ¢äº†ï¼ˆæ¥è‡ªåŽŸå§‹é—®é¢˜ï¼‰

if (data[c] >= 128) 
    sum += data[c];

与

if (data[c] >= 128) 
    sum = data[c];

未排åºçš„数组给出约。åŒæ ·çš„结果,我想问为什么分支预测在这ç§æƒ…况下ä¸èµ·ä½œç”¨ï¼Ÿ

1 个答案:

答案 0 :(得分:10)

我用jmhæ¥åˆ†æžè¿™ä¸ªã€‚这是我的代ç ï¼š

@OutputTimeUnit(TimeUnit.MICROSECONDS)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 2, time = 1)
@Measurement(iterations = 3, time = 1)
@State(Scope.Thread)
@Fork(2)
public class Comparison
{
  static final int SIZE = 1<<15;
  final int[] data = new int[SIZE];

  @Setup
  public void setup() {
    int i = 1;
    for (int c = 0; c < SIZE; ++c) data[c] = (i*=611953);
    for (int c = 0; c < SIZE; ++c) data[c] = data[c] >= 128? 128 : 127;
  }

  @GenerateMicroBenchmark
  public long sum() {
    long sum = 0;
    for (int c = 0; c < SIZE; ++c) if (data[c] >= 128) sum += data[c];
    return sum;
  }
}

注æ„我ä¸ä½¿ç”¨æŽ’åºæˆ–éšæœºæ•°ç”Ÿæˆ;它们是ä¸å¿…è¦çš„并å‘症。使用上é¢ä»£ç ä¸­ä½¿ç”¨çš„å…¬å¼ï¼š

data[c] = (i*=611953);

我的è¿è¡Œæ—¶é—´ä¸º132μs。如果我注释掉涉åŠ

的行
data[c] = data[c] >= 128? 128 : 127;

时间根本没有å˜åŒ–。这消除了所有算术考虑,并侧é‡äºŽåˆ†æ”¯é¢„测。如果我使用

data[c] = 127;

我得到13μs,如果我使用

data[c] = 128;

我得到16μs。这是“基本案例â€ï¼Œå¼ºè°ƒäº†ä¸æ–­åˆ†æ”¯å†³ç­–之间的区别。

我的结论:这ç»å¯¹æ˜¯ä½Žçº§åˆ†æ”¯é¢„测的结果。

JITå¯ä»¥å转循环å—?

让我们现在分æžä½ çš„干预。如果我使用上é¢ä»£ç ä¸­æ供的公å¼ï¼Œä½†æ›´æ”¹

if (data[c] >= 128) sum += data[c];

到

if (data[c] >= 128) sum = data[c];

然åŽæ—¶é—´ç¡®å®žä»Ž132μs下é™åˆ°27μs。

这是我对解释掉è½çš„猜测:JIT编译器å¯ä»¥åšçš„优化技巧是å转循环的方å‘。现在您的代ç å˜ä¸º

for (int c = SIZE-1; c <= 0; --c) if (data[c] >= 128) { sum = data[c]; break; }

循环已ç»çŸ­è·¯åˆ°è¾¾åˆ°ä¸ŽåŽŸå§‹å¾ªçŽ¯ç›¸åŒç»“果所需的最å°è¿­ä»£æ¬¡æ•°ã€‚

我添加了这个

data[SIZE-1] = 128;

到setup()方法的末尾,但它没有改å˜æ—¶é—´ã€‚这似乎使“循环逆转â€çŒœæƒ³çš„天真版本无效。

ä¸ï¼Œå®ƒå¯èƒ½æ˜¯cmovl

在分æžè£…é…时,我å‘现:

cmp edx, 0x80
cmovl eax, ebx

cmovl是一个æ¡ä»¶ç§»åŠ¨æŒ‡ä»¤ï¼Œå®ƒå°†æ‰§è¡Œthen分支中å‘生的赋值效果,但ä¸æ¶‰åŠä»»ä½•è·³è½¬ï¼Œå› æ­¤æ¶ˆé™¤äº†ä¸Žä¹‹ç›¸å…³çš„任何惩罚分支预测失败。这是对实际效果的一个很好的解释。