2015-06-05 21 views
8

Jestem ciekawy, czy for..in powinien być preferowany do .each ze względu na wydajność.Groovy: Czy dla ... znacznie szybciej niż .each?

+2

Co próbowaliście do tej pory? Zauważ, że JMH (http://openjdk.java.net/projects/code-tools/jmh/) ma groovy szablon, który ułatwia (ale nie jest trywialne) stworzenie benchmarku w niesamowitym stylu. Pamiętaj, aby wrócić wraz ze swoimi odkryciami! – llogiq

+0

@llogiq dzięki za wzmiankę o jmh! Dam ci szansę. –

+6

Dla mnie zmiana wszystkich "każdego" na "for-in" jest prawdopodobnie ostatnią z listy technik optymalizacji. Mówiąc po prostu :-) Lub mówiąc inaczej, jedno niepotrzebne wywołanie DB może równać się tysiącom zoptymalizowanych 'for-ins'. – defectus

Odpowiedz

4

For .. in jest częścią standardowej kontroli przepływu języka.

Zamiast tego each wywołuje zamknięcie z dodatkowym obciążeniem.

.each {...} cukier jest składnia równoważne wywołaniu metody .each({...})

Ponadto, ze względu na fakt, że jest to zamknięcie, wewnątrz bloku kodu each nie można wykorzystać break i continue sprawozdań do kontroli pętli.

http://kunaldabir.blogspot.it/2011/07/groovy-performance-iterating-with.html

Aktualizacja odniesienia Java 1.8.0_45 Groovy 2.4.3:

  • 3327981 każdy {}
  • 320949 o() {

Oto inny wzorzec z 100000 iteracje:

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 

Wyniki:

  • 261062715 każdy {}
  • 64518703 do() {}
+2

to są dane 4-letnie. nigdy nie ufaj wzorcowi, którego sam nie sfałszowałeś. – cfrick

+0

masz rację, ale jest to połączone z istotą, każdy może ponownie uruchomić benchmark. Po prostu robię to z java8, a wynikiem jest: 3056480/8181870/3327981/320949 – frhack

+0

Jak długo trwa lista? – weston

4

Rzućmy okiem teoretycznej na rzeczy w kategoriach tego, co połączenia wykonywane są dynamiczne i jakie połączenia są wykonywane więcej bezpośrednio z logiką Java (wywołuję te statyczne wywołania).

W przypadku for-in, Groovy działa na Iteratorze, aby go uzyskać, mamy jedno dynamiczne wywołanie iteratora(). Jeśli się nie mylę, hasNext i następne wywołania są wykonywane przy użyciu zwykłej logiki wywołania metody Java. Tak więc dla każdej iteracji mamy tutaj tylko 2 statyczne wywołania. W zależności od benchmarku należy zauważyć, że to pierwsze wywołanie iterator() może spowodować poważne czasy inicjalizacji, ponieważ może to zainicjować system meta-klasy, a to zajmuje chwilę.

W przypadku each mamy wywołanie dynamiczne do każdego z nich, jak również tworzenie obiektu dla bloku otwartego (wystąpienie zamknięcia). each (Closure) następnie wywoła iterator(), ale uncached ... cóż, wszystkie jednorazowe koszty. Podczas pętli hasNext i dalej są wykonywane przy użyciu logiki Java, która wykonuje 2 statyczne wywołania. Wywołanie do wystąpienia zamknięcia odbywa się za pomocą standardowej logiki java dla wywołania metody, która następnie wywoła metodę doCall za pomocą wywołania dynamicznego.

Podsumowując, za iterację for-in używa tylko 2 wywołania statyczne, a each ma 3 wywołania statyczne i 1 dynamiczne. Wywołanie dynamiczne jest znacznie wolniejsze niż wiele wywołań statycznych i znacznie trudniejsze do optymalizacji pod kątem JVM, tym samym dominując w czasie. W wyniku tego each powinien zawsze być wolniejszy, o ile blok otwarty wymaga wywołania dynamicznego.

Z powodu skomplikowanej logiki wywołania Closure # trudno jest zoptymalizować wywołanie dynamiczne. I to jest denerwujące, ponieważ tak naprawdę nie jest potrzebne i zostanie usunięte, gdy tylko znajdziemy obejście.Jeśli kiedykolwiek nam się to uda, each może nadal działać wolniej, ale jest o wiele trudniejsze, ponieważ odgrywają tu rolę rozmiary kodu bajtowego i profile wywoływania. Teoretycznie mogą być równe (ignorując czas inicjacji), ale JVM ma znacznie więcej pracy do wykonania. Oczywiście to samo dotyczy przykładów strumieniowego przetwarzania lambda w Java8,

+0

W języku Java8 forEach z zamknięciem wydaje się szybszy niż w przypadku standardowej pętli. https://gist.github.com/frhack/5db0485f9847e6b673be – frhack

+0

@fhack musisz uważać na takie mikro-znaki. musisz dać mu odpowiedni czas rozgrzania, a najlepiej przetestować tylko jeden konstrukt w tym samym czasie. Jestem pewien, że czasy w obu są bardzo podobne, jeśli testowane z tymi ograniczeniami. Ale są oczywiście różnice w wielkości kodu. Wersja forEach ma dla każdej metody plus lambda, która musi zostać zoptymalizowana. Normalna wersja pętli for ma korpus pętli, ale także metodę, którą ciało pętli ma zoptymalizować. Dlatego nie jest dobrze, aby pętla była łatwa w teście ze wszystkim innym. – blackdrag

+0

Powoduje to duże kodowanie części do optymalizacji i może spowodować, że kompilator nie będzie tego robić. Na końcu forEach wykonuje te same kroki logiczne co normalna pętla, ale z większą liczbą wywołań metod pomiędzy nimi. Optymalizacja trwa dłużej ... ale na końcu oba powinny być równe. Wszystko inne oznaczałoby dla mnie zmarnowany potencjał. Nie ma dla mnie żadnego technicznego powodu, dla którego twój przykład powinien zachowywać się inaczej – blackdrag

Powiązane problemy