Отказ от ответственности
Все написанное ниже относится только к JVM HotSpot.
Короткие ответы
компилятор JIT не выполняет встроенные или статические вызовы методов, поскольку выполняемый метод зависит от типа.
Это противоположно истине. Смотрите мой ответ.
Есть ли разница в производительности между использованием Collections.emptyList() или пустым ArrayList, особенно при использовании JIT-компилятора?
В редких случаях - да. См. результаты микробенчмарка.
Если бы я вызывал этот метод только с ArrayList, компилятор JIT мог бы встроить ArrayList.get(). Если бы я также делал вызовы с Collections.empty(), это было бы невозможно. Это правильно?
Короткий ответ - это зависит. Компилятор JIT достаточно умен, чтобы распознавать мономорфные, биморфные и полиморфные шаблоны вызовов и обеспечивать соответствующие реализации.
Отвечать
Чтобы получить развернутый ответ, рекомендую прочитать следующий пост о черной магии диспетчеризации методов. В нескольких словах
C2 выполняет интересную оптимизацию на основе профиля на основе наблюдаемого профиля типа. Если имеется только один тип получателя (то есть сайт вызова мономорфный), он может просто проверить предсказанный тип и напрямую встроить цель. Та же самая оптимизация может и будет применяться, если наблюдаются два типа получателей (то есть сайт вызова биморфный), за счет двух ветвей.
Рассмотрим следующий пример JMH (если вы не знали о 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
для 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
для 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
.
person
Ivan Mamontov
schedule
26.10.2015
foo(List<Bar> list)
иfoo(ArrayList<Bar> list)
, верно? Если нет, уточните. Почтовый индекс. Но в любом случае, сделайте свой дизайн чистым, вместо того, чтобы пытаться преждевременно оптимизировать то, что в любом случае не нужно оптимизировать. Java использует пулипоризм, и JIT может с этим справиться. Не беспокойтесь об этом. - person JB Nizet   schedule 24.10.2015