Ответ 1
TL; раздел DR
Хорошие новости
Ваше измерение действительно создает реальный эффект.
Плохая новость
Это происходит в основном случайно, потому что ваш тест имеет много технических недостатков, и эффект, который он предоставляет, вероятно, не тот, который вы имеете в виду.
Подход new Character()
быстрее, если и только если HotSpot Escape Analysis успешно доказывает, что результирующий экземпляр можно безопасно распределить в стеке вместо кучи. Поэтому эффект не так важен, как подразумевается в вашем вопросе.
Объяснение эффекта
Причина, по которой new Character()
быстрее, - это локальность ссылки: ваш экземпляр находится в стеке, и весь доступ к нему осуществляется через образы кэша CPU. Когда вы повторно используете кешированный экземпляр, вы должны
- получить доступ к удаленному полю
static
; - разыщите его в удаленном массиве;
- разыменовать запись массива в удаленный экземпляр
Character
; - присоединяется к
char
, содержащемуся в этом экземпляре.
Каждое разыменование является потенциальной ошибкой кэша процессора. Кроме того, он заставляет часть кэша перенаправляться в эти удаленные местоположения, что приводит к большему количеству промахов в кеше на входной строке и/или в местах расположения стека.
ПОДРОБНОСТИ
Я запустил этот код с помощью jmh
:
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@BenchmarkMode(Mode.AverageTime)
public class Chars {
static String string = "12345678901234567890"; static {
for (int i = 0; i < 10; i++) string += string;
}
@GenerateMicroBenchmark
public void newChar() {
int len = string.length();
for (int i = 0; i < len; i++) new Character(string.charAt(i));
}
@GenerateMicroBenchmark
public void justChar() {
int len = string.length();
for (int i = 0; i < len; i++) Character.valueOf(string.charAt(i));
}
}
Это сохраняет суть вашего кода, но устраняет некоторые систематические ошибки, такие как разминка и время компиляции. Вот результаты:
Benchmark Mode Thr Cnt Sec Mean Mean error Units
o.s.Chars.justChar avgt 1 3 5 39.062 6.587 usec/op
o.s.Chars.newChar avgt 1 3 5 19.114 0.653 usec/op
И это было бы моим лучшим предположением о том, что происходит:
-
в
newChar
вы создаете новый экземплярCharacter
. Анализ Escape с помощью HotSpot может доказать, что экземпляр никогда не ускользает, поэтому он позволяет распределять стек или в специальном случаеCharacter
может полностью исключить выделение, поскольку данные из него, по-видимому, никогда не используются; -
в
justChar
вы включаете поиск в массив кешаCharacter
, который имеет некоторую стоимость.
UPDATE
В ответ на критику Алекса, я добавил еще несколько методов к эталону. Основной эффект остается стабильным, но мы получаем еще более мелкие детали о меньших эффектах оптимизации.
@GenerateMicroBenchmark
public int newCharUsed() {
int len = string.length(), sum = 0;
for (int i = 0; i < len; i++) sum += new Character(string.charAt(i));
return sum;
}
@GenerateMicroBenchmark
public int justCharUsed() {
int len = string.length(), sum = 0;
for (int i = 0; i < len; i++) sum += Character.valueOf(string.charAt(i));
return sum;
}
@GenerateMicroBenchmark
public void newChar() {
int len = string.length();
for (int i = 0; i < len; i++) new Character(string.charAt(i));
}
@GenerateMicroBenchmark
public void justChar() {
int len = string.length();
for (int i = 0; i < len; i++) Character.valueOf(string.charAt(i));
}
@GenerateMicroBenchmark
public void newCharValue() {
int len = string.length();
for (int i = 0; i < len; i++) new Character(string.charAt(i)).charValue();
}
@GenerateMicroBenchmark
public void justCharValue() {
int len = string.length();
for (int i = 0; i < len; i++) Character.valueOf(string.charAt(i)).charValue();
}
ОПИСАНИЕ:
- базовые версии:
justChar
иnewChar
; -
...Value
методы добавляют вызовcharValue
к базовой версии; -
...Used
методы добавляют вызовcharValue
(неявно) и используют значение, чтобы исключить любое исключение Dead Code.
Результаты:
Benchmark Mode Thr Cnt Sec Mean Mean error Units
o.s.Chars.justChar avgt 1 3 1 246.847 5.969 usec/op
o.s.Chars.justCharUsed avgt 1 3 1 370.031 26.057 usec/op
o.s.Chars.justCharValue avgt 1 3 1 296.342 60.705 usec/op
o.s.Chars.newChar avgt 1 3 1 123.302 10.596 usec/op
o.s.Chars.newCharUsed avgt 1 3 1 172.721 9.055 usec/op
o.s.Chars.newCharValue avgt 1 3 1 123.040 5.095 usec/op
- есть свидетельства об исключении Dead Code Elimination (DCE) как в вариантах
justChar
, так иnewChar
, но это только частичное; - с вариантом
newChar
, добавлениеcharValue
не имеет никакого эффекта, поэтому, по-видимому, это был DCE'd; - с
justChar
,charValue
имеет эффект, поэтому, похоже, он не был устранен; - DCE имеет незначительный общий эффект, о чем свидетельствует устойчивая разница между
newCharUsed
иjustCharUsed
.