Comments 19
Ваша ссылка на историю изменений в ArrayList неправильная. Вот лучше: http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/log/73d5bcd0585d/src/share/classes/java/util/ArrayList.java
Вообще таких недоработок в JDK вагон и маленькая тележка. Они не хотят раздувать код классов коллекций, оптимизируя каждый вариант вызова каждого метода. Хотя с моей точки зрения, стоило бы.
Конкретизируйте? Насколько я знаю, в JIT нет общего лимита на количество или общий объем скомпилированных методов. Имеет значение глубина стека и размер конкретного метода в байтах, но замена общих методов из какого-нибудь AbstractList на оптимизированные реализации ни на то, ни на другое не влияет.
При росте общего объема машинного кода уменьшается эффективность использования кеша инструкций, но, во-первых, это не относится к JIT, а во-вторых, не так велик эффект… лучше оптимальный метод вне кеша инструкций, чем неоптимальный в кеше.
А-а-а, ты везде!!! Приходишь на работу — там новые фичареквесты от тебя. Идёшь почитать, что в джаве делается, а там письма от тебя в мейлинг-листе. Хочешь передохнуть, заглядываешь на Хабр, и тут ты!!!
Кстати, пометка HotSpotIntrinsicCandidate
не означает, что это на самом деле интринсик, и уж точно не «позволяет ВМ создавать для них высокопроизводительный машинный код». Чтобы сделать интринсик одной пометки мало. И помечено существенно больше методов, чем на самом деле интринсиков (на то оно и «candidate»). Поэтому закладываться на это не стоит.
Кстати, можно ещё приложиться к Arrays.asList. Там вроде SubList вообще специально не реализован, а мог бы быть.
Верно, в документации сказано:
It indicates that an annotated method may be (but is not guaranteed to be) intrinsified by the HotSpot VM. A method is intrinsified if the HotSpot VM replaces the annotated method with hand-written assembly and/or hand-written compiler IR — a compiler intrinsic — to improve performance.
Думаю, вместо "создавать для них высокопроизводительный машинный код" мне стоило написать "подменять их реализацию высокопроизводительным машинным кодом". Кстати, интересно было бы узнать, в каких условиях интринсификация не срабатывает.
В Arrays.asList много чего не реализовано, например hashCode(): в текущей реализации вызывается метод абстрактного списка, использующий итератор, хотя можно было бы написать
@Override
public int hashCode() {
return Arrays.hashCode(a);
и обойтись без перебора итератором.
в каких условиях интринсификация не срабатывает.
Ну так кури сорцы :D
и обойтись без перебора итератором.
Надо, кстати, проверить. Теоретически его итератор добили до состояния, когда он может удалиться полностью escape-анализом в плотном цикле.
Проверил с помощью -prof gc
, действительно, даже на восьмёрке итератор не создаётся: gc.alloc.rate.norm
постоянно около 0. По времени счётный перебор конечно же быстрее, хотя и ненамного.
А вот для Collection.containsAll()
скаляризация срабатывает только на небольших объёмах:
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(jvmArgsAppend = {"-XX:+UseParallelGC", "-Xms1g", "-Xmx1g"})
public class AllMatchVsContainsAllBenchmark {
@Benchmark
public boolean collectionContainsAll(Data data) {
return data.original.containsAll(data.half);
}
@State(Scope.Thread)
public static class Data {
@Param({"10", "100", "1000"})
int count;
@Param({"ArrayList", "HashSet"})
String collectionType;
Collection<Integer> half;
Collection<Integer> original;
@Setup
public void setup() {
if ("ArrayList".equals(collectionType)) {
original = IntStream.range(0, count).boxed().collect(toCollection(ArrayList::new));
half = new HashSet<>(halfOfCollection(original));
} else {
original = IntStream.range(0, count).boxed().collect(toCollection(HashSet::new));
half = new HashSet<>(halfOfCollection(original));
}
}
private List<Integer> halfOfCollection(Collection<Integer> original) {
int newLength = original.size() / 2;
Integer[] integers = copyOf(original.toArray(new Integer[0]), newLength);
return asList(integers);
}
}
}
даёт
Benchmark (collectionType) (count) Score Error Units
gc.alloc.rate.norm ArrayList 10 ≈ 10⁻⁵ B/op
gc.alloc.rate.norm ArrayList 100 0,001 ± 0,001 B/op
gc.alloc.rate.norm ArrayList 1000 40,066 ± 0,003 B/op
gc.alloc.rate.norm HashSet 10 ≈ 10⁻⁴ B/op
gc.alloc.rate.norm HashSet 100 ≈ 10⁻⁴ B/op
gc.alloc.rate.norm HashSet 1000 20,004 ± 6,817 B/op
Даже в Java 9 ArrayList всё ещё можно (и нужно) улучшать