Jestem nowy w Javie 8. Nadal nie znam dokładnie API, ale zrobiłem mały nieformalny test porównawczy, aby porównać wydajność nowego API Streams ze starymi, dobrymi kolekcjami.
Badanie polega na filtrowanie listy Integer
i dla każdego numeru nawet obliczyć pierwiastek kwadratowy i przechowywanie go w rezultacie List
o Double
.
Oto kod:
public static void main(String[] args) {
//Calculating square root of even numbers from 1 to N
int min = 1;
int max = 1000000;
List<Integer> sourceList = new ArrayList<>();
for (int i = min; i < max; i++) {
sourceList.add(i);
}
List<Double> result = new LinkedList<>();
//Collections approach
long t0 = System.nanoTime();
long elapsed = 0;
for (Integer i : sourceList) {
if(i % 2 == 0){
result.add(Math.sqrt(i));
}
}
elapsed = System.nanoTime() - t0;
System.out.printf("Collections: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));
//Stream approach
Stream<Integer> stream = sourceList.stream();
t0 = System.nanoTime();
result = stream.filter(i -> i%2 == 0).map(i -> Math.sqrt(i)).collect(Collectors.toList());
elapsed = System.nanoTime() - t0;
System.out.printf("Streams: Elapsed time:\t\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));
//Parallel stream approach
stream = sourceList.stream().parallel();
t0 = System.nanoTime();
result = stream.filter(i -> i%2 == 0).map(i -> Math.sqrt(i)).collect(Collectors.toList());
elapsed = System.nanoTime() - t0;
System.out.printf("Parallel streams: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));
}.
A oto wyniki dla maszyny dwurdzeniowej:
Collections: Elapsed time: 94338247 ns (0,094338 seconds)
Streams: Elapsed time: 201112924 ns (0,201113 seconds)
Parallel streams: Elapsed time: 357243629 ns (0,357244 seconds)
W tym konkretnym teście strumienie są około dwa razy wolniejsze niż kolekcje, a równoległość nie pomaga (lub używam go w niewłaściwy sposób?).
Pytania:
- Czy ten test jest sprawiedliwy? Czy popełniłem jakiś błąd?
- Czy strumienie są wolniejsze niż zbiory? Czy ktoś zrobił w tej sprawie dobry formalny punkt odniesienia?
- Do jakiego podejścia powinienem dążyć?
Zaktualizowane wyniki.
Przeprowadziłem test 1k razy po rozgrzewce JVM (1k iteracji), zgodnie z radą @pveentjer:
Collections: Average time: 206884437,000000 ns (0,206884 seconds)
Streams: Average time: 98366725,000000 ns (0,098367 seconds)
Parallel streams: Average time: 167703705,000000 ns (0,167704 seconds)
W tym przypadku strumienie są bardziej wydajne. Zastanawiam się, co można by zaobserwować w aplikacji, w której funkcja filtrująca jest wywoływana tylko raz lub dwa razy w czasie działania.
toList
powinno działać równolegle, nawet jeśli jest zbierane do listy, która nie jest bezpieczna dla wątków, ponieważ różne wątki będą gromadzić się na listach pośrednich ograniczonych wątkami przed scaleniem.
IntStream
zamiast tego?