Java BigDecimal奇怪的性能行为

时间:2015-05-27 06:08:24

标签: java performance

今天我遇到了BigDecimal的一个奇怪的表现行为。简单来说,试图做同样事情的下面两段代码之间存在显着差异

int hash = foo();
BigDecimal number = new BigDecimal(hash);

VS

BigDecimal number = new BigDecimal(foo());

为了证明这一点,我在下面的课程中显示了不同之处。我的java是1.7.0_75-b13,64位,mac。在我的环境中,第一个循环花了2s,第二个循环花了5s。

import java.math.BigDecimal;

public class Crazy {

public static void main(String[] args) {
    new Crazy().run();
}

void run() {
    // init

    long count = 1000000000l;

    // start test 1

    long start = System.currentTimeMillis();

    long sum = 0;
    for (long i=0; i<count; i++) {
        sum = add(sum);
    }

    long end = System.currentTimeMillis();
    System.out.println(end - start);

    // start test 2

    long start2 = end;
    sum = 0;
    for (long i=0; i<count; i++) {
        sum = add1(sum);
    }

    long end2 = System.currentTimeMillis();
    System.out.println(end2 - start2);
}

long add(long sum) {
    int hash = hashCode();
    BigDecimal number = new BigDecimal(hash);
    sum += number.longValue();
    return sum;
}

long add1(long sum) {
    BigDecimal number = new BigDecimal(hashCode());
    sum += number.longValue();
    return sum;
}
}

javap输出

long add(long);
Code:
   0: aload_0       
   1: invokevirtual #56                 // Method java/lang/Object.hashCode:()I
   4: istore_3      
   5: new           #60                 // class java/math/BigDecimal
   8: dup           
   9: iload_3       
  10: invokespecial #62                 // Method java/math/BigDecimal."<init>":(I)V
  13: astore        4
  15: lload_1       
  16: aload         4
  18: invokevirtual #65                 // Method java/math/BigDecimal.longValue:()J
  21: ladd          
  22: lstore_1      
  23: lload_1       
  24: lreturn       

long add1(long);
Code:
   0: new           #60                 // class java/math/BigDecimal
   3: dup           
   4: aload_0       
   5: invokevirtual #56                 // Method java/lang/Object.hashCode:()I
   8: invokespecial #62                 // Method java/math/BigDecimal."<init>":(I)V
  11: astore_3      
  12: lload_1       
  13: aload_3       
  14: invokevirtual #65                 // Method java/math/BigDecimal.longValue:()J
  17: ladd          
  18: lstore_1      
  19: lload_1       
  20: lreturn      

2 个答案:

答案 0 :(得分:2)

我使用以下基准重现了对Java 1.7.0.79的影响:

import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;

import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.annotations.*;

@Warmup(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 10, time = 3, timeUnit = TimeUnit.SECONDS)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Fork(2)
@State(Scope.Benchmark)
public class AddTest {
    long add(long sum) {
        int hash = hashCode();
        BigDecimal number = new BigDecimal(hash);
        sum += number.longValue();
        return sum;
    }

    long add1(long sum) {
        BigDecimal number = new BigDecimal(hashCode());
        sum += number.longValue();
        return sum;
    }

    @Benchmark
    public void testAdd(Blackhole bh) {
        long count = 100000000l;
        long sum = 0;
        for (long i=0; i<count; i++) {
            sum = add(sum);
        }
        bh.consume(sum);
    }

    @Benchmark
    public void testAdd1(Blackhole bh) {
        long count = 100000000l;
        long sum = 0;
        for (long i=0; i<count; i++) {
            sum = add1(sum);
        }
        bh.consume(sum);
    }
}

结果如下:

# JMH 1.9 (released 40 days ago)
# VM invoker: C:\Program Files\Java\jdk1.7.0_79\jre\bin\java.exe
# VM options: <none>

Benchmark         Mode  Cnt     Score    Error  Units
AddTest.testAdd   avgt   20   214.740 ±  4.323  ms/op
AddTest.testAdd1  avgt   20  1138.269 ± 32.062  ms/op

有趣的是,使用1.8.0.25的结果完全相反:

# JMH 1.9 (released 40 days ago)
# VM invoker: C:\Program Files\Java\jdk1.8.0_25\jre\bin\java.exe
# VM options: <none>

Benchmark         Mode  Cnt     Score    Error  Units
AddTest.testAdd   avgt   20  1126.126 ± 22.120  ms/op
AddTest.testAdd1  avgt   20   217.145 ±  1.905  ms/op

然而,在1.8.0_40版本中,两个版本都很快:

# JMH 1.9 (released 40 days ago)
# VM invoker: C:\Program Files\Java\jdk1.8.0_40\jre\bin\java.exe
# VM options: <none>

Benchmark         Mode  Cnt    Score   Error  Units
AddTest.testAdd   avgt   20  218.925 ± 5.093  ms/op
AddTest.testAdd1  avgt   20  217.066 ± 1.427  ms/op

在所有这些情况下,addadd1方法都会内联到调用方法中。似乎它与JIT编译器中的循环展开机制的内部更改有关:有时你的循环很好地展开,有时它不是。

答案 1 :(得分:1)

我无法重现这一点。请考虑以下Microbenchmark:

@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class BigDecimalBenchmark {

  static int i = 1024;

  @Benchmark
  public BigDecimal constructor() {
    return new BigDecimal(foo());
  }

  @Benchmark
  public BigDecimal localVariable() {
    int hash = foo();
    return new BigDecimal(hash);
  }

  private static int foo() {
    return i;
  }

}

其中给出了以下输出:

Benchmark                             Mode  Samples       Score      Error   Units
BigDecimalBenchmark.constructor      thrpt      100  180368.227 ± 4280.269  ops/ms
BigDecimalBenchmark.localVariable    thrpt      100  173519.036 ±  868.547  ops/ms

<强>更新

编辑基准以使foo()不可内联。