Мне интересно, если for..in
должен быть достоин .each
по соображениям безопасности.Groovy: Является ли это для .. значительно быстрее, чем?
ответ
For .. in
является частью стандартного управления потоками языка.
Вместо этого each
вызывает закрытие, поэтому с дополнительными накладными расходами.
.each {...}
является синтаксис эквивалентен вызову метода .each({...})
Кроме того, в связи с тем, что этим замыканием внутри each
блока кода вы не можете использовать break
и continue
заявления для контроля цикла.
http://kunaldabir.blogspot.it/2011/07/groovy-performance-iterating-with.html
Обновленный тест Java 1.8.0_45 Groovy 2.4.3:
- 3327981 каждый {}
- 320949 для() {
Вот еще один тест с 100000 итерации:
lines = (1..100000)
// with list.each {}
start = System.nanoTime()
lines.each { line->
line++;
}
println System.nanoTime() - start
// with loop over list
start = System.nanoTime()
for (line in lines){
line++;
}
println System.nanoTime() - start
результаты:
- 261062715 каждый {}
- 64518703 для() {}
Давайте теоретический взгляд на вещи с точки зрения того, что звонки осуществляются динамична и то, что звонки делаются более непосредственно с логикой Java (я буду называть эти статические вызовы).
В случае for-in
, Groovy работает на Итераторе, чтобы получить его, у нас есть один динамический вызов итератору(). Если я не ошибаюсь, hasNext и следующие вызовы выполняются с использованием обычной логики вызова метода Java. Таким образом, для каждой итерации мы имеем только 2 статических вызова. В зависимости от эталона следует отметить, что этот первый вызов iterator() может вызвать серьезные времена инициализации, поскольку это может инициировать систему метакласса, и это занимает некоторое время.
В случае each
у нас есть динамический вызов для каждого из них, а также создание объекта для открытого блока (экземпляр Closure). каждый (Closure) будет вызывать итератор(), но не закрыт ... ну все разовые затраты. Во время цикла hasNext и следующий выполняются с использованием Java-логики, которая делает 2 статических вызова. Вызов в экземпляр Closure выполняется с помощью стандартной логики Java для вызова метода, который затем будет вызывать doCall с использованием динамического вызова.
Подводя итог, за каждую итерацию for-in
использует только 2 статических вызова, а each
имеет 3 статических и 1 динамический вызов. Динамический вызов намного медленнее, чем несколько статических вызовов, и гораздо труднее оптимизировать для JVM, тем самым доминируя над временем. В результате этого each
всегда должен быть медленнее, если для открытого блока требуется динамический вызов.
Из-за сложной логики вызова Closure # трудно оптимизировать динамический вызов. И это раздражает, потому что это действительно не нужно и будет удалено, как только мы найдем обходное решение.Если нам это удастся, each
может все еще быть медленнее, но это намного сложнее, поскольку здесь важны размеры байтов и профили вызовов. Теоретически они могут быть равны тогда (игнорируя время init), но JVM имеет гораздо больше работы. Конечно, то же самое относится к примерам, основанным на обработке лямбды на основе потока в Java8,
В Java8 forEach с закрытием кажется быстрым, чем стандартный для цикла. https://gist.github.com/frhack/5db0485f9847e6b673be – frhack
@frhack вы должны быть осторожны с такими микрообъектами. вы должны дать ему надлежащее время прогрева, и лучше всего проверить только одну конструкцию в одно и то же время. Я уверен, что времена в обоих очень похожи, если они тестируются с этими ограничениями. Но, конечно, различия в размере кода. Версия forEach имеет метод for-each и лямбда, который необходимо оптимизировать. Обычная версия для цикла имеет тело цикла, но также метод, в котором находится тело цикла для оптимизации. Вот почему нехорошо иметь равную петлю в тесте со всем остальным. – blackdrag
Это приводит к большому кодовому размеру части для оптимизации и может привести к тому, что компилятор не сделает этого. В конце forEach выполняет те же логические шаги, что и обычный цикл, но с большим количеством вызовов методов между ними. Поэтому для оптимизации требуется больше времени ... но в итоге оба должны быть равны. Все остальное означало бы потраченный впустую потенциал. Для меня нет технической причины, чтобы ваш пример приводил к – blackdrag
Что вы пробовали? Обратите внимание, что JMH (http://openjdk.java.net/projects/code-tools/jmh/) имеет отличный шаблон, который упрощает (но не является тривиальным) создание теста в groovy. Обязательно верните свои результаты! – llogiq
@llogiq спасибо за упоминание jmh! Я сделаю это. –
Для меня изменение всех 'each' на' for-in', вероятно, является последним в списке методов оптимизации. Просто говоря :-) Или, говоря иначе, один ненужный вызов БД может равняться 1000 оптимизированным 'for-ins'. – defectus