Я пытался воспроизвести некоторые из эффектов кэша процессора, описанные в здесь. Я понимаю, что Java - это управляемая среда, и эти примеры не будут точно переводить, но я столкнулся с странным случаем, который я попытался перевести на простой пример, иллюстрирующий эффект:
public static void main(String[] args) {
final int runs = 10;
final int steps = 1024 * 1024 * 1024;
for (int run = 0; run < runs; run++) {
final int[] a = new int[1];
long start = System.nanoTime();
for (int i = 0; i < steps; i++) {
a[0]++;
}
long stop = System.nanoTime();
long time = TimeUnit.MILLISECONDS.convert(stop - start, TimeUnit.NANOSECONDS);
System.out.printf("Time for loop# %2d: %5d ms\n", run, time);
}
}
Вывод:
Time for loop# 0: 24 ms
Time for loop# 1: 106 ms
Time for loop# 2: 104 ms
Time for loop# 3: 103 ms
Time for loop# 4: 102 ms
Time for loop# 5: 103 ms
Time for loop# 6: 104 ms
Time for loop# 7: 102 ms
Time for loop# 8: 105 ms
Time for loop# 9: 102 ms
Первая итерация внутреннего цикла примерно в 4 раза быстрее, чем последующие итерации. Это противоположность тому, что я обычно ожидал бы, так как обычно performace поднимается по мере того, как JIT пинает.
Конечно, можно было бы сделать несколько циклов прогрева в любом серьезном микро-контроле, но мне любопытно, что может вызвать такое поведение, тем более, что, если мы знаем, что цикл может быть выполнен за 24 мс, это не очень удовлетворяя, что установившееся время составляет более 100 мсек.
Для справки JDK я использую (в linux):
openjdk version "1.8.0_40"
OpenJDK Runtime Environment (build 1.8.0_40-b20)
OpenJDK 64-Bit Server VM (build 25.40-b23, mixed mode)
UPDATE:
Вот некоторые сведения об обновлении, основанные на некоторых комментариях, и некоторые эксперименты:
1) перемещение входа/выхода System.out из цикла (путем хранения синхронизации в массиве "прогонов" размера) не приводит к существенным различиям во времени.
2) вывод, отображаемый выше, - это когда я запускаю из Eclipse. Когда я компилирую и запускаю из командной строки (с тем же JDK/JVM), я получаю более скромные, но все же значительные результаты (2x вместо 4x быстрее). Это кажется интересным, так как usaully работает в затмении замедлит все, что угодно.
3) перемещение a
вверх, из цикла, так что он будет повторно использован, каждая итерация не имеет эффекта.
4), если int[] a
изменено на long[] a
, первая итерация выполняется еще быстрее (около 20%), в то время как другие итерации по-прежнему остаются той же (медленной) скоростью.
ОБНОВЛЕНИЕ 2:
Я думаю, что ответ Апангин объясняет это. Я попробовал это с JVM Sun 1.9, и это происходит от:
openjdk version "1.8.0_40"
OpenJDK Runtime Environment (build 1.8.0_40-b20)
OpenJDK 64-Bit Server VM (build 25.40-b23, mixed mode)
Time for loop# 0: 48 ms
Time for loop# 1: 116 ms
Time for loop# 2: 112 ms
Time for loop# 3: 113 ms
Time for loop# 4: 112 ms
Time for loop# 5: 112 ms
Time for loop# 6: 111 ms
Time for loop# 7: 111 ms
Time for loop# 8: 113 ms
Time for loop# 9: 113 ms
в
java version "1.9.0-ea"
Java(TM) SE Runtime Environment (build 1.9.0-ea-b73)
Java HotSpot(TM) 64-Bit Server VM (build 1.9.0-ea-b73, mixed mode)
Time for loop# 0: 48 ms
Time for loop# 1: 26 ms
Time for loop# 2: 22 ms
Time for loop# 3: 22 ms
Time for loop# 4: 22 ms
Time for loop# 5: 22 ms
Time for loop# 6: 22 ms
Time for loop# 7: 22 ms
Time for loop# 8: 22 ms
Time for loop# 9: 23 ms
Это вполне улучшилось!