Oto inna technika, z którą spotkałem się tamtego dnia:
Collections.nCopies(8, 1)
.stream()
.forEach(i -> System.out.println(i));
Collections.nCopies
Wezwanie tworzy List
zawierających n
kopie cokolwiek wartość podać. W tym przypadku jest to Integer
wartość w ramce 1. Oczywiście w rzeczywistości nie tworzy listy z n
elementami; tworzy „zwirtualizowaną” listę, która zawiera tylko wartość i długość, a każde wywołanie get
w zakresie po prostu zwraca wartość. Ta nCopies
metoda istnieje od czasu wprowadzenia struktury kolekcji w JDK 1.2. Oczywiście możliwość tworzenia strumienia z jego wyniku została dodana w Javie SE 8.
Wielka sprawa, inny sposób na zrobienie tego samego w tej samej liczbie linii.
Jednak technika ta jest szybsza niż IntStream.generate
i IntStream.iterate
podejść, i zaskakująco, ale także szybciej niż IntStream.range
podejścia.
Dla iterate
a generate
wynik nie jest może zbyt zaskakujące. Struktura strumieni (tak naprawdę Spliteratory dla tych strumieni) jest zbudowana przy założeniu, że lambdy będą potencjalnie generować różne wartości za każdym razem i będą generować nieograniczoną liczbę wyników. To sprawia, że równoległe rozłupywanie jest szczególnie trudne. iterate
Metoda jest również problematyczne w tym przypadku, ponieważ każde wywołanie wymaga wynik poprzedniego. Zatem strumienie używające generate
i iterate
nie radzą sobie zbyt dobrze z generowaniem powtarzających się stałych.
range
Zaskakujące jest stosunkowo słabe działanie programu . To również jest zwirtualizowane, więc w rzeczywistości nie wszystkie elementy istnieją w pamięci, a rozmiar jest znany z góry. Powinno to zapewnić szybki i łatwy równolegle rozdzielacz. Ale, co zaskakujące, nie wyszło zbyt dobrze. Być może powodem jest to, że range
musi obliczyć wartość dla każdego elementu zakresu, a następnie wywołać na nim funkcję. Ale ta funkcja po prostu ignoruje swoje dane wejściowe i zwraca stałą, więc jestem zaskoczony, że nie jest ona wstawiana i zabijana.
Collections.nCopies
Technika musi zrobić boks / unboxing w celu obsługi wartości, ponieważ nie istnieją prymitywne specjalizacje List
. Ponieważ wartość jest za każdym razem taka sama , w zasadzie jest ona zapakowana raz i to pudełko jest wspólne dla wszystkich n
kopii. Podejrzewam, że boxing / unboxing jest wysoce zoptymalizowany, a nawet zintensyfikowany i można go dobrze wprowadzić.
Oto kod:
public static final int LIMIT = 500_000_000;
public static final long VALUE = 3L;
public long range() {
return
LongStream.range(0, LIMIT)
.parallel()
.map(i -> VALUE)
.map(i -> i % 73 % 13)
.sum();
}
public long ncopies() {
return
Collections.nCopies(LIMIT, VALUE)
.parallelStream()
.mapToLong(i -> i)
.map(i -> i % 73 % 13)
.sum();
}
A oto wyniki JMH: (2,8 GHz Core2Duo)
Benchmark Mode Samples Mean Mean error Units
c.s.q.SO18532488.ncopies thrpt 5 7.547 2.904 ops/s
c.s.q.SO18532488.range thrpt 5 0.317 0.064 ops/s
W wersji ncopies występuje spora rozbieżność, ale ogólnie wydaje się, że jest ona 20 razy szybsza niż wersja z zakresu. (Chociaż byłbym skłonny uwierzyć, że zrobiłem coś złego).
Jestem zaskoczony, jak dobrze nCopies
działa ta technika. Wewnętrznie nie robi to zbyt wiele, ponieważ strumień zwirtualizowanej listy jest po prostu implementowany za pomocą IntStream.range
! Spodziewałem się, że konieczne będzie stworzenie wyspecjalizowanego rozdzielacza, aby to działało szybko, ale już wydaje się, że jest całkiem niezły.