Czasami chcesz filtrować Stream
z więcej niż jednym warunkiem:
myList.stream().filter(x -> x.size() > 10).filter(x -> x.isCool()) ...
lub możesz zrobić to samo ze złożonym warunkiem i jednym filter
:
myList.stream().filter(x -> x.size() > 10 && x -> x.isCool()) ...
Domyślam się, że drugie podejście ma lepszą charakterystykę wydajności, ale nie wiem .
Pierwsze podejście wygrywa w czytelności, ale co jest lepsze dla wydajności?
Odpowiedzi:
Kod, który należy wykonać dla obu alternatyw, jest tak podobny, że nie można w wiarygodny sposób przewidzieć wyniku. Podstawowa struktura obiektu może się różnić, ale nie stanowi to wyzwania dla optymalizatora hotspotów. To zależy od innych warunków otoczenia, które ulegną szybszemu wykonaniu, jeśli będzie jakaś różnica.
Połączenie dwóch instancji filtra tworzy więcej obiektów, a tym samym więcej kodu delegującego, ale może się to zmienić, jeśli użyjesz referencji metod zamiast wyrażeń lambda, np. Zamień
filter(x -> x.isCool())
nafilter(ItemType::isCool)
. W ten sposób wyeliminowano syntetyczną metodę delegowania utworzoną dla wyrażenia lambda. Zatem połączenie dwóch filtrów przy użyciu dwóch odwołań do metod może stworzyć ten sam lub mniejszy kod delegacji niż pojedynczefilter
wywołanie przy użyciu wyrażenia lambda z&&
.Ale, jak powiedziano, tego rodzaju koszty ogólne zostaną wyeliminowane przez optymalizator HotSpot i są nieistotne.
Teoretycznie dwa filtry mogłyby być łatwiejsze do zrównoleglenia niż pojedynczy filtr, ale dotyczy to tylko intensywnych obliczeniowych zadań¹.
Więc nie ma prostej odpowiedzi.
Podsumowując, nie myśl o takich różnicach wydajności poniżej progu wykrywania zapachu. Użyj tego, co jest bardziej czytelne.
¹… i wymagałoby implementacji wykonującej równoległe przetwarzanie kolejnych etapów, która obecnie nie jest realizowana przez standardowe wdrożenie Stream
źródło
Złożony warunek filtrowania jest lepszy z punktu widzenia wydajności, ale najlepsza wydajność pokaże starą modę dla pętli ze standardem
if clause
jest najlepszą opcją. Różnica na małej 10-elementowej różnicy może być ~ 2 razy, dla dużej tablicy różnica nie jest tak duża.Możesz rzucić okiem na mój projekt GitHub , w którym przeprowadziłem testy wydajności dla wielu opcji iteracji macierzy
Dla małych układów 10-elementowa przepustowość operacji / s: Dla średnich 10 000 elementów-przepustowość operacji / s: Dla dużych układów 1 000 000 elementów przepustowość operacji / s:
UWAGA: testy trwają
AKTUALIZACJA: Java 11 ma pewien postęp w wydajności, ale dynamika pozostaje taka sama
Tryb testu: Przepustowość, operacje / czas
źródło
Ten test pokazuje, że Twoja druga opcja może działać znacznie lepiej. Najpierw ustalenia, a następnie kod:
teraz kod:
źródło
Test #1: {count=100, sum=7207, min=65, average=72.070000, max=91} Test #3: {count=100, sum=7959, min=72, average=79.590000, max=97} Test #2: {count=100, sum=8869, min=79, average=88.690000, max=110}
Jest to wynik 6 różnych kombinacji przykładowego testu udostępnionego przez @Hank D. Jest oczywiste, że predykat formy
u -> exp1 && exp2
jest bardzo wydajny we wszystkich przypadkach.źródło