免责声明
下面写的所有内容仅适用于热点 JVM https://en.wikipedia.org/wiki/HotSpot.
简短的答案
JIT 编译器不执行内联或静态方法调用,因为
执行的方法取决于类型。
这与事实相反。看我的回答。
使用之间是否有性能差异
Collections.emptyList() 或空 ArrayList,尤其是在使用
JIT 编译器?
在极少数情况下 - 是的。查看微基准测试结果。
如果我只使用 ArrayList 调用此方法,JIT 编译器可以
内联 ArrayList.get()。如果我也使用 Collections.empty() 进行调用
这是不可能的。那是对的吗?
简短的回答 - 这取决于。 JIT 编译器足够智能,可以识别单态、双态和多态调用模式并提供适当的实现。
Answer
为了获得详细的答案,我建议阅读以下内容post http://shipilev.net/blog/2015/black-magic-method-dispatch/关于方法调度的黑魔法。简而言之
C2 基于以下内容进行了有趣的配置文件引导优化
观察到的类型概况。如果只有一个接收器类型(即
是,调用站点是单态),它可以简单地检查
预测类型,并直接内联目标。同样的优化
如果观察到两种接收器类型(即
是,调用站点是双态),以两个分支为代价。
让我们考虑以下 JMH 示例(如果您还没有了解 JMH 那么我建议阅读它here http://openjdk.java.net/projects/code-tools/jmh/).
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(value = 5)
public class ExampleBench {
@Param("10000")
private int count;
List<Integer>[] arrays;
List<Integer>[] empty;
List<Integer>[] bimorphic;
List<Integer>[] polimorphic;
@Setup
public void setup(){
Random r = new Random(0xBAD_BEEF);
arrays = new List[count];
empty = new List[count];
bimorphic = new List[count];
polimorphic = new List[count];
for (int i = 0; i < arrays.length; i++) {
bimorphic[i] = r.nextBoolean() ? new ArrayList<Integer>(0) : Collections.<Integer>emptyList();
int i1 = r.nextInt(3);
switch (i1) {
case 0 : polimorphic[i] = new ArrayList<>(0);
break;
case 1 : polimorphic[i] = new LinkedList<>();
break;
case 2 : polimorphic[i] = Collections.emptyList();
break;
}
arrays[i] = new ArrayList<>(0);
empty[i] = Collections.emptyList();
}
}
@Benchmark
public float arrayList() {
List<Integer>[] l = arrays;
int c = count;
float result = 0;
for (int i = 0; i < c; i++) {
result += sum(l[i]);
}
return result;
}
@Benchmark
public float emptyList() {
List<Integer>[] l = empty;
int c = count;
float result = 0;
for (int i = 0; i < c; i++) {
result += sum(l[i]);
}
return result;
}
@Benchmark
public float biList() {
List<Integer>[] l = bimorphic;
int c = count;
float result = 0;
for (int i = 0; i < c; i++) {
result += sum(l[i]);
}
return result;
}
@Benchmark
public float polyList() {
List<Integer>[] l = polimorphic;
int c = count;
float result = 0;
for (int i = 0; i < c; i++) {
result += sum(l[i]);
}
return result;
}
int sum(List<Integer> list) {
int sum = 0;
for (int i = 0; i < list.size(); ++i) {
sum += list.get(i);
}
return sum;
}
}
结果是:
Benchmark (count) Mode Cnt Score Error Units
ExampleBench.arrayList 10000 avgt 5 22902.547 ± 27665.651 ns/op
ExampleBench.biList 10000 avgt 5 50459.552 ± 739.379 ns/op
ExampleBench.emptyList 10000 avgt 5 3745.469 ± 211.794 ns/op
ExampleBench.polyList 10000 avgt 5 164879.943 ± 5830.008 ns/op
在单态和双态调用的情况下,JIT 用具体实现代替虚拟调用。例如,如果arrayList()
我们有以下输出-XX:+PrintInlining
:
@ 27 edu.jvm.runtime.ExampleBench::sum (38 bytes) inline (hot)
@ 6 java.util.ArrayList::size (5 bytes) accessor
\-> TypeProfile (15648/15648 counts) = java/util/ArrayList
for emptyList()
:
@ 27 edu.jvm.runtime.ExampleBench::sum (38 bytes) inline (hot)
@ 6 java.util.Collections$EmptyList::size (2 bytes) inline (hot)
\-> TypeProfile (9913/9913 counts) = java/util/Collections$EmptyList
for biList()
:
@ 27 edu.jvm.runtime.ExampleBench::sum (38 bytes) inline (hot)
@ 6 java.util.Collections$EmptyList::size (2 bytes) inline (hot)
@ 6 java.util.ArrayList::size (5 bytes) accessor
\-> TypeProfile (2513/5120 counts) = java/util/ArrayList
\-> TypeProfile (2607/5120 counts) = java/util/Collections$EmptyList
的情况下polyList()
JIT 不内联任何实现并使用真正的虚拟调用。
在这些方法中使用内联函数有什么优点?让我们看看编译器生成的代码arrayList()
:
0x00007ff9e51bce50: cmp $0xf80036dc,%r10d ;instance of 'java/util/ArrayList'
0x00007ff9e51bce57: jne L0000 ;if false go to L0000 (invokeinterface size)
0x00007ff9e51bce59: mov 0x10(%rdx),%ebp ;*getfield size optimization java.util.ArrayList::size@1
.....
0x00007ff9e51bce6d: retq
L0000: mov $0xffffffde,%esi ; true virtual call starts here
0x00007ff9e51bce73: mov %rdx,(%rsp)
0x00007ff9e51bce77: callq 0x00007ff9e50051a0 ; OopMap{[0]=Oop off=92}
;*invokeinterface size
; - edu.jvm.runtime.ExampleBench::sum@6 (line 119)
; {runtime_call}
如您所见,JIT 通过以下方式替换了虚拟调用getfield
.