public class GCCostBenchmark { @Param( { "32", "64", "128", "256", "1024" } ) public static int SIZE = 128; ArrayList<Integer> list = new ArrayList<>(); @Setup public void setup() { final ThreadLocalRandom rnd = ThreadLocalRandom.current(); for( int i = 0; i < SIZE; i++ ) { list.add( rnd.nextInt() ); } } @Benchmark public long sumWithIterator() { long sum = 0; for( final Integer n : list ) { sum += n; } return sum; } @Benchmark public long sumWithIndex() { long sum = 0; for( int i = 0; i < list.size(); i++ ) { final Integer n = list.get( i ); sum += n; } return sum; } }Просто создаем
ArrayList
разных размеров, заполняем его случайными числами, и в цикле суммируем. И смотрим на два разных варианта организации цикла: через итератор, и старперский, через индекс.
Benchmark (SIZE) Mode Cnt Score Error Units sumWithIndex 32 thrpt 5 27.195 ± 3.476 ops/us sumWithIndex 64 thrpt 5 15.362 ± 0.443 ops/us sumWithIndex 128 thrpt 5 8.359 ± 0.775 ops/us sumWithIndex 256 thrpt 5 4.243 ± 0.268 ops/us sumWithIndex 1024 thrpt 5 1.115 ± 0.029 ops/us sumWithIterator 32 thrpt 5 24.300 ± 0.244 ops/us sumWithIterator 64 thrpt 5 12.973 ± 0.056 ops/us sumWithIterator 128 thrpt 5 7.415 ± 0.035 ops/us sumWithIterator 256 thrpt 5 4.023 ± 0.392 ops/us sumWithIterator 1024 thrpt 5 1.138 ± 0.012 ops/usВидно, что на моей JDK 1.8.0_102 они идут ноздря в ноздрю: вариант с индексом слегка обходит итератор, но различие в пределах погрешностей. Ок, это будет наша реперная точка. Теперь мы запускаем тот же бенчмарк, но с флагом
-XX:-EliminateAllocations
. В у бенчмарка с итератором, разумеется, появляются строчки gc-profiler-а gc.alloc.rate.norm=32.000±0.001 B/op
, но меня интересуют другие цифры:
Benchmark (SIZE) Mode Cnt Score Error Units sumWithIndex 32 thrpt 5 27.063 ± 1.527 ops/us sumWithIndex 64 thrpt 5 15.571 ± 0.243 ops/us sumWithIndex 128 thrpt 5 7.795 ± 0.066 ops/us sumWithIndex 256 thrpt 5 4.213 ± 0.022 ops/us sumWithIndex 1024 thrpt 5 1.120 ± 0.011 ops/us sumWithIterator 32 thrpt 5 21.022 ± 1.452 ops/us sumWithIterator 64 thrpt 5 11.295 ± 2.082 ops/us sumWithIterator 128 thrpt 5 6.145 ± 0.273 ops/us sumWithIterator 256 thrpt 5 3.359 ± 0.035 ops/us sumWithIterator 1024 thrpt 5 0.905 ± 0.032 ops/usКак и можно было бы ожидать, итерация с индексом почти никак не отреагировала на этот флаг, а итератор стал медленнее, примерно на ~15-20%. Но это я волевым решением отрезал скаляризацию. А давайте сэмулируем отсутствие инлайнинга (и как следствие — скаляризации):
-XX:CompileCommand="dontinline,java.util.AbstractList::*" -XX:CompileCommand="dontinline,java.util.ArrayList::*"— я отключаю инлайнинг для всех методов
ArrayList/AbstractList
(ну, чтобы уж с надежностью):
Benchmark (SIZE) Mode Cnt Score Error Units sumWithIndex 32 thrpt 5 2.767 ± 0.033 ops/us sumWithIndex 64 thrpt 5 1.410 ± 0.012 ops/us sumWithIndex 128 thrpt 5 0.709 ± 0.003 ops/us sumWithIndex 256 thrpt 5 0.357 ± 0.003 ops/us sumWithIndex 1024 thrpt 5 0.093 ± 0.003 ops/us sumWithIterator 32 thrpt 5 3.528 ± 0.055 ops/us sumWithIterator 64 thrpt 5 1.821 ± 0.029 ops/us sumWithIterator 128 thrpt 5 0.933 ± 0.015 ops/us sumWithIterator 256 thrpt 5 0.464 ± 0.020 ops/us sumWithIterator 1024 thrpt 5 0.119 ± 0.003 ops/usБез инлайнинга производительность цикла просела в 10 раз! (кстати, теперь версия с итератором работает быстрее :)
Пример немного искусственный: уж больно много разных loop optimizations отваливается без инлайнинга. Для кода более общего вида настолько радикального падения производительности не будет. Но в целом, мораль истории такова: если у вас где-то в горячем коде не сработал инлайнинг — у вас скорее всего уже достаточно серьезные проблемы с производительностью в этом месте. Не скаляризованные аллокации добавят сюда лишь какие-то крохи.
Комментариев нет:
Отправить комментарий