2015-05-27 2 views
3

Сегодня я столкнулся с необычным поведением с BigDecimal. В простом слове, существует значительная разница между следующими двумя кусками кода пытается сделать то же самоеJava BigDecimal странное поведение производительности

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

против

BigDecimal number = new BigDecimal(foo()); 

, чтобы доказать это, я ниже класс, чтобы показать разница. Мой java - 1.7.0_75-b13, 64 бит, mac. В моей среде первый цикл занял 2 секунды, второй цикл занял 5 секунд.

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  
+4

Звучит как какой-то эффект «микро-бенчмарка» для меня, обе формы эквивалентны –

+1

Попробуйте изменить порядок 'add()' и 'add1()' в коде и посмотреть, что произойдет? – ntalbs

+0

Пожалуйста, разместите выход javap для двух подходов. –

ответ

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 

Update

Отредактированный эталоном сделать Foo() не inlineable.

+0

Какую версию JVM вы используете? Я могу [воспроизвести] (https://gist.github.com/amaembo/110c4499398d66880a73/edit) это. –

+0

1.8.0u31, Java 7 - конец жизни –

+0

Тем не менее, вопрос был о Java 7. –

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 

Во всех этих случаях add и add1 методов встраиваемый в метод вызывающего абонента. Похоже, что это связано только с внутренними изменениями с механизмом разворота цикла в JIT-компиляторе: иногда ваш цикл хорошо разворачивается, иногда это не так.

Смежные вопросы