Java BigDecimal strange performance behavior

I ran into unusual behavior with BigDecimal today. In a simple word, there is a significant difference between the following two pieces of code trying to do the same.

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

      

against

BigDecimal number = new BigDecimal(foo());

      

to prove it I have the class below to show the difference. My java is 1.7.0_75-b13, 64 bit, mac. In my environment, the first loop took 2 seconds, the second loop took 5 seconds.

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 output

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      

      

+3


source to share


2 answers


I cannot reproduce this. Consider the following 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;
  }

}

      

Which gives the following output:



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

      

Update

Edited test to make foo () not inline.

+1


source


I have reproduced the effect in Java 1.7.0.79 using the following test:

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

      

The results are as follows:

# 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

      

It's funny that using 1.8.0.25 the results are exactly the opposite:



# 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

      

However, on 1.8.0_40 both versions are fast:

# 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

      

In all these cases, the methods of the method add

and add1

are built into the method of the caller. It looks like this is only due to internal changes to the loop unrolling mechanism in the JIT compiler: sometimes your loop unfolds well, sometimes it doesn't.

+2


source







All Articles