Scroll down to read English version (not implemented yet)
##ArrayInstantiationBenchmark
Показывает разницу между созданием массива с помощью конструктора и вызовом платформенно-зависимого метода Array::newInstance
:
@Benchmark
public Object newInstance() {
return Array.newInstance(Object.class, length);
}
@Benchmark
public Object constructor() {
return new Object[length];
}
В коде, скомпилированном С2 между ними нет особой разницы, однако в режиме интерпретатора и С1 конструктор отрабатывает значительно быстрее.
Интересен больша разница во времени при создании небольшого массива и режиме С1:
(length) Mode Cnt Score Error Units
constructor 10 avgt 30 6,5 ± 0,1 ns/op
newInstance 10 avgt 30 68,3 ± 4,4 ns/op
constructor 100 avgt 30 34,8 ± 0,8 ns/op
newInstance 100 avgt 30 84,9 ± 4,1 ns/op
Платформенно-зависимый метод работает почти в 10 раз медленнее, чем конструктор, при создании массива из 10 объектов. При создании же массива из 100 объектов разница между двумя способами составляет всего 2,4 раза.
Это поведение наблюдается только для С1.
См. итоги измерений в файле ./results/ArrayInstantiationBenchmark.txt
##ArithmeticSimplificationBenchmark
Показывает разницу между выражением с избыточным приведением и без него:
@Benchmark
public int slow(Data data) {
int size = data.size;
int initialCapacity = data.initialCapacity;
return (int) ((initialCapacity + size - 1L) / size);
}
@Benchmark
public int fast(Data data) {
int size = data.size;
int initialCapacity = data.initialCapacity;
return (initialCapacity + size - 1) / size;
}
ВАЖНО! Способ, описанный в методе fast
не всегда работает правильно,
См. ArithmeticTest
Поэтому замена 1L
на 1
предпочтительна только для положительных чисел.
См. итоги измерений в файле ./results/ArithmeticSimplificationBenchmark.txt
##SubListToArrayBenchmark
Показывает проседание производительности при вызове java.util.ArrayList$SubList::toArray
:
@Benchmark
public Integer[] list(Data holder) {
return holder.list.toArray(new Integer[0]);
}
@Benchmark
public Integer[] subList(Data holder) {
return holder.list.subList(0, holder.size).toArray(new Integer[0]);
}
Несмотря на одинаковый объём передаваемых данных, производителньость второго метода значительно хуже первого.
См. обсуждение http://mail.openjdk.java.net/pipermail/core-libs-dev/2018-January/051102.html
Задача: https://bugs.openjdk.java.net/browse/JDK-8196207
Исправлено в JDK 11.
См. итоги измерений в файле ./results/SubListToArrayBenchmark.txt
##ZeroingEliminationBenchmark
Несколько странный пример, показывающий ненужность ручного обнуления содрежимого массива, т.к. согласно спецификации его отдельные ячейки и так заполнены значениями по умолчанию. Т.е. для целых чисел это 0.
См. итоги измерений в файле ./results/ZeroingEliminationBenchmark.txt
##BoxingBenchmark
Пример, показывающий значительное проседание производительности при использовании обёртки (Long
) для
сложения чисел.
В боевом коде проблема была обнаружена в org.springframework.data.jpa.repository.support.SimpleJpaRepository
.
См. spring-projects/spring-data-jpa#269
Начиная с издания 2018.2 "Идея" умеет обнаруживать подобный код (см. https://youtrack.jetbrains.com/issue/IDEA-189336).
См. итоги измерений в файле ./results/BoxingBenchmark.txt
##CollectionsAddAllVsAddAllBenchmark
Пример, показывающий неправдивость документации к методу java.util.Collections::addAll
для коллекций, основанных на массиве
или предназначенных для использования в многопоточной среде.
Рассмотрим пример:
@Benchmark
public boolean collectionsAddAll(Data data) {
Collection<Integer> collection = data.freshCollection();
Integer[] array = data.array;
return Collections.addAll(collection, array);
}
@Benchmark
public boolean addAll(Data data) {
Collection<Integer> collection = data.freshCollection();
List<Integer> arrayAsList = Arrays.asList(data.array);
return collection.addAll(arrayAsList);
}
В современных изданиях JDK метод collectionsAddAll
работает быстрее только для java.util.HashSet
, при это разница очень незначительна.
Для прочих коллекций collectionsAddAll
работает значительно медленнее, хотя документация утверждает обратное.
См. задачу https://bugs.openjdk.java.net/browse/JDK-8193031
Предлагаемое изменение: http://cr.openjdk.java.net/~martin/webrevs/jdk/Collections-addAll/
Обсуждение: http://mail.openjdk.java.net/pipermail/core-libs-dev/2018-January/051197.html
См. итоги измерений в файлах для соответсвующих типов данных:
- ./results/CollectionsAddAllVsAddAllBenchmark_HashSet.txt
- ./results/CollectionsAddAllVsAddAllBenchmark_COWList.txt
- ./results/CollectionsAddAllVsAddAllBenchmark_ArrayList.txt
##ConcurrentHashMapBenchmark
Показывает падение производительности в JDK 8 из-за ошибки в реализации метода ConcurrentHashMap::computeIfAbsent
.
Рассмотрим код:
private ConcurrentHashMap<Integer, Integer> map = new ConcurrentHashMap<>();
@Benchmark
@Threads(2)
public Integer computeIfAbsent() {
return map.computeIfAbsent(key, function);
}
@Benchmark
@Threads(2)
public Integer getAndPut() {
Integer key = this.key;
Integer value = map.get(key);
if (value == null) {
value = function.apply(key);
map.put(key, value);
}
return value;
}
Несмотря на то, что оба примера делают одно и тоже, использование ConcurrentHashMap::computeIfAbsent
сильно замедляет исполнение, т.к. блокировка применяется ко всему методу, а не только к той части, которая
кладёт значение при отсутствии ключа.
Описание: https://bugs.openjdk.java.net/browse/JDK-8161372
Исправление: http://hg.openjdk.java.net/jdk9/dev/jdk/rev/fe0d3813e6c3
Проблема, вызванная данной: https://jira.spring.io/browse/DATACMNS-1396
См. итоги измерений в файле ./results/ConcurrentHashMapBenchmark.txt
##StringBuilderAppendBenchmark
Показывает разницу между двумя способами добавления подстроки к StringBuilder
-у:
//1 способ
String append(String str, int from, int to) {
return new StringBuilder().append('.').append(str, from, to).append('.').toString();
}
//2 способ
String append(String str, int from, int to) {
String subStr = str.substring(from, to);
return new StringBuilder().append('.').append(str).append('.').toString();
}
Второй способ можно привести к ещё более простому виду:
String append(String str, int from, int to) {
return '.' + str.substring(from, to) + '.';
}
Опыт показывает, что несмотря на схожесть обоих подходов, второй показывает лучшую производительность, см. итоги измерений в файле ./results/StringBuilderAppendBenchmark.txt
Причина в том, что метод StringBuilder.append(String)
интринзифицирован, а StringBuilder.append(String, int, int)
делегирует вызов методу AbstractStringBuilder.append(String, int, int)
, внутри которого строка добавляется познаково
с мопощью цикла в методе AbstractStringBuilder.appendChars(CharSequence, int, int)
.
##CharacterReplaceBenchmark Показывает разницу между двумя способами замены всех вхождений знака в строке на другой знак. Первый способ:
public StringBuilder manualReplace(Class<String> klass) {
String name = klass.getName();
int nameLength = name.length();
StringBuilder stringBuilder = new StringBuilder(nameLength);
for (int i = 0; i < nameLength; ++i) {
char car = name.charAt(i);
stringBuilder.append(car == '.' ? '/' : car);
}
return stringBuilder;
}
Второй способ:
public String stringReplace(Class<String> klass) {
return klass.getName().replace('.', '/');
}
Пример из жизни: https://github.com/spring-projects/spring-framework/pull/22299/files
Второй способ проще и даёт лучшую производительность. Вот итог замера для класс java.lang.String
:
CharacterReplaceBenchmark.manualReplace avgt 25 58,474 ± 4,184 ns/op
CharacterReplaceBenchmark.manualReplace:·gc.alloc.rate.norm avgt 25 112,000 ± 0,001 B/op
CharacterReplaceBenchmark.stringReplace avgt 25 15,185 ± 0,067 ns/op
CharacterReplaceBenchmark.stringReplace:·gc.alloc.rate.norm avgt 25 56,000 ± 0,001 B/op
##VolatileFieldBenchmark Показывает ненужность (и вредность для производительности) присваивания значения по умолчанию волатильному полю, что вынуждает исполнение вставлять дополнительные инструкции для сохранения happens-before поведения даже при отсутствии связанных чтений/записей. Создание объекта этого класса стоит значительно дороже
private static class ExplicitInit {
private volatile boolean field = false;
}
чем вот этого:
private static class NoInit {
private volatile boolean field;
}
Отсюда проистекает избыточность явного зануления объектов атомарных классов:
@State(Scope.Thread)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(value = Mode.AverageTime)
public class AtomicBenchmark {
@Benchmark
public Object defaultValue() {
return new AtomicInteger();
}
@Benchmark
public Object explicitValue() {
return new AtomicInteger(0);
}
}
Метод explicitValue
работает в 2,5 раза дольше (Java 8):
Benchmark Mode Cnt Score Error Units
AtomicBenchmark.defaultValue avgt 30 4.778 ± 0.403 ns/op
AtomicBenchmark.explicitValue avgt 30 11.846 ± 0.273 ns/op
и
Benchmark Mode Cnt Score Error Units
VolatileFieldZeroingBenchmark.explicitInit avgt 100 8.285 ± 0.022 ns/op
VolatileFieldZeroingBenchmark.noInit avgt 100 3.465 ± 0.029 ns/op
Примеры из жизни:
- openjdk/jdk#218
- openjdk/jdk#818
- spring-projects/spring-framework#25261
- spring-projects/spring-framework#25846
Также см.
- подробное объяснение ненужности явного присваивания http://cs.oswego.edu/pipermail/concurrency-interest/2015-December/014767.html
- задача для создания соответствующей проверки https://youtrack.jetbrains.com/issue/IDEA-243752