I'm new to Java 8. I still don't know the API in depth, but I've made a small informal benchmark to compare the performance of the new Streams API vs the good old Collections.
The test consists in filtering a list of Integer
, and for each even number, calculate the square root and storing it in a result List
of Double
.
Here is the code:
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));
}.
And here are the results for a dual core machine:
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)
For this particular test, streams are about twice as slow as collections, and parallelism doesn't help (or either I'm using it the wrong way?).
Questions:
- Is this test fair? Have I made any mistake?
- Are streams slower than collections? Has anyone made a good formal benchmark on this?
- Which approach should I strive for?
Updated results.
I ran the test 1k times after JVM warmup (1k iterations) as advised by @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)
In this case streams are more performant. I wonder what would be observed in an app where the filtering function is only called once or twice during runtime.
Stop using
LinkedList
for anything but heavy removing from the middle of the list using iterator.Stop writing benchmarking code by hand, use JMH.
Proper benchmarks:
Result:
Just as I expected stream implementation is fairly slower. JIT is able to inline all lambda stuff but doesn't produce as perfectly concise code as vanilla version.
Generally, Java 8 streams is not a magic. They couldn't speedup already well-implemented things (with, probably, plain iterations or Java 5's for-each statements replaced with
Iterable.forEach()
andCollection.removeIf()
calls). Streams are more about coding convenience and safety. Convenience -- speed tradeoff is working here.For what you are trying to do, I would not use regular java api's anyway. There is a ton of boxing/unboxing going on, so there is a huge performance overhead.
Personally I think that a lot of API designed are crap because they create a lot of object litter.
Try to use a primitive arrays of double/int and try to do it single threaded and see what the performance is.
PS: You might want to have a look at JMH to take care of doing the benchmark. It takes care of some of the typical pitfalls like warming up the JVM.
I change the code a bit, ran on my mac book pro which has 8 cores, I got a reasonable result:
Collections: Elapsed time: 1522036826 ns (1.522037 seconds)
Streams: Elapsed time: 4315833719 ns (4.315834 seconds)
Parallel streams: Elapsed time: 261152901 ns (0.261153 seconds)
1) You see time less than 1 second using you benchmark. That means there can be strong influence of side effects on your results. So, I increased your task 10 times
and ran your benchmark. My results:
without edit (
int max = 1000000
) results wereIt's like your results: stream is slower than collection. Conclusion: much time were spent for stream initialization/values transmitting.
2) After increasing task stream became faster (that's OK), but parallel stream remained too slow. What's wrong? Note: you have
collect(Collectors.toList())
in you command. Collecting to single collection essentially introduces performance bottleneck and overhead in case of concurrent execution. It is possible to estimate the relative cost of overhead by replacingFor streams it can be done by
collect(Collectors.counting())
. I got results:That' s for a big task! (
int max = 10000000
) Conclusion: collecting items to collection took majority of time. The slowest part is adding to list. BTW, simpleArrayList
is used forCollectors.toList()
.