我在 Java 17 上運行以下基準測驗:
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(jvmArgsAppend = {"-Xms2g", "-Xmx2g"})
public class ValueOfBenchmark {
private final int value = 12345;
@Benchmark
public String concat() {
return "" value;
}
@Benchmark
public String valueOf() {
return String.valueOf(value);
}
}
編譯后我有這個位元組碼
public concat()Ljava/lang/String;
@Lorg/openjdk/jmh/annotations/Benchmark;()
L0
LINENUMBER 21 L0
LDC "12345"
ARETURN
L1
LOCALVARIABLE this Lcom/tsypanov/ovn/ValueOfBenchmark; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
public valueOf()Ljava/lang/String;
@Lorg/openjdk/jmh/annotations/Benchmark;()
L0
LINENUMBER 26 L0
SIPUSH 12345
INVOKESTATIC java/lang/String.valueOf (I)Ljava/lang/String;
ARETURN
L1
LOCALVARIABLE this Lcom/tsypanov/ovn/ValueOfBenchmark; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
Here I conclude that javac constant-folded the value of accessed field at compile time. In concat method it went even further and predicted eventual value to be returned from the method, but failed to do the same for valueOf() and as a result constant value of 12345 is pushed onto the stack with subsequent call to String.valueOf().
This benchmark gives the following results:
Benchmark Mode Cnt Score Error Units
ValueOfBenchmark.concat avgt 40 1,665 ± 0,006 ns/op
ValueOfBenchmark.valueOf avgt 40 4,475 ± 0,217 ns/op
Then I remove final from value field declaration and recompile the benchmark:
public concat()Ljava/lang/String;
@Lorg/openjdk/jmh/annotations/Benchmark;()
L0
LINENUMBER 21 L0
ALOAD 0
GETFIELD com/tsypanov/ovn/ValueOfBenchmark.value : I
INVOKEDYNAMIC makeConcatWithConstants(I)Ljava/lang/String; [
// handle kind 0x6 : INVOKESTATIC
java/lang/invoke/StringConcatFactory.makeConcatWithConstants(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
// arguments:
"\u0001"
]
ARETURN
L1
LOCALVARIABLE this Lcom/tsypanov/ovn/ValueOfBenchmark; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
public valueOf()Ljava/lang/String;
@Lorg/openjdk/jmh/annotations/Benchmark;()
L0
LINENUMBER 26 L0
ALOAD 0
GETFIELD com/tsypanov/ovn/ValueOfBenchmark.value : I
INVOKESTATIC java/lang/String.valueOf (I)Ljava/lang/String;
ARETURN
L1
LOCALVARIABLE this Lcom/tsypanov/ovn/ValueOfBenchmark; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
Now we have invokedynamic for concat() method, but valueOf() remained basically the same, the only difference is that int value is read from the field. This however brought significant regression of results:
Benchmark Mode Cnt Score Error Units
ValueOfBenchmark.concat avgt 40 9,829 ± 0,059 ns/op
ValueOfBenchmark.valueOf avgt 40 10,238 ± 0,463 ns/op
For sure I expected this for concat() method as now we do concat two values into one String.
但令我困惑的是valueOf()方法的回歸。
我假設String.valueOf()在基準方法中呼叫的成本保持不變,那么為什么欄位訪問變得如此昂貴?
uj5u.com熱心網友回復:
我假設在基準方法中呼叫 String.valueOf() 的成本保持不變
它不是。在第一種情況下,方法引數是常量,因此 JIT 可以應用常量傳播優化。
考慮一個非常簡化的例子:
static char getLastDigit(int n) {
return (char) ((n % 10) '0');
}
char c1 = getLastDigit(123);
char c2 = getLastDigit(this.n);
當getLastDigit使用常量呼叫時,不需要執行實際的除法或加法 - JIT 可能會替換整個呼叫char c1 = '3';它顯然不能對變數進行相同的優化。
當然,Integer.getChars它更復雜,但當使用常量呼叫時,它仍然可能會跳過一些比較和算術運算。
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/456733.html
