2015-06-05 2 views
8

Мне интересно, если for..in должен быть достоин .each по соображениям безопасности.Groovy: Является ли это для .. значительно быстрее, чем?

+2

Что вы пробовали? Обратите внимание, что JMH (http://openjdk.java.net/projects/code-tools/jmh/) имеет отличный шаблон, который упрощает (но не является тривиальным) создание теста в groovy. Обязательно верните свои результаты! – llogiq

+0

@llogiq спасибо за упоминание jmh! Я сделаю это. –

+6

Для меня изменение всех 'each' на' for-in', вероятно, является последним в списке методов оптимизации. Просто говоря :-) Или, говоря иначе, один ненужный вызов БД может равняться 1000 оптимизированным 'for-ins'. – defectus

ответ

4

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 для() {}
+2

это четырехлетние данные. никогда не доверяйте бенчмарку, который вы не сфальсифицировали. – cfrick

+0

Вы правы, но это связано с gist, каждый может снова запустить тест. Я просто делаю это с java8, и результат: 3056480/8181870/3327981/320949 – frhack

+0

Как долго? – weston

4

Давайте теоретический взгляд на вещи с точки зрения того, что звонки осуществляются динамична и то, что звонки делаются более непосредственно с логикой 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,

+0

В Java8 forEach с закрытием кажется быстрым, чем стандартный для цикла. https://gist.github.com/frhack/5db0485f9847e6b673be – frhack

+0

@frhack вы должны быть осторожны с такими микрообъектами. вы должны дать ему надлежащее время прогрева, и лучше всего проверить только одну конструкцию в одно и то же время. Я уверен, что времена в обоих очень похожи, если они тестируются с этими ограничениями. Но, конечно, различия в размере кода. Версия forEach имеет метод for-each и лямбда, который необходимо оптимизировать. Обычная версия для цикла имеет тело цикла, но также метод, в котором находится тело цикла для оптимизации. Вот почему нехорошо иметь равную петлю в тесте со всем остальным. – blackdrag

+0

Это приводит к большому кодовому размеру части для оптимизации и может привести к тому, что компилятор не сделает этого. В конце forEach выполняет те же логические шаги, что и обычный цикл, но с большим количеством вызовов методов между ними. Поэтому для оптимизации требуется больше времени ... но в итоге оба должны быть равны. Все остальное означало бы потраченный впустую потенциал. Для меня нет технической причины, чтобы ваш пример приводил к – blackdrag