我是Java 8的新手,对API的了解还不够深入,但我做了一个小的非正式基准测试来比较新的Streams API和好的旧集合的性能。
测试包括过滤Integer
列表,对于每个偶数,计算平方根并将其存储在Double
的结果List
中。
代码如下:
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));
}.
下面是双核机器的结果:
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)
对于这个特殊的测试,流的速度大约是集合的两倍,并行没有帮助(或者我用错了方式?)。
问题:
已更新结果。
在JVM预热(1k次迭代)之后,我按照@pveentjer的建议运行了1k次测试:
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)
在这种情况下,流的性能更好。我想知道在运行时过滤函数只被调用一次或两次的应用程序中会发生什么。
发布于 2014-03-27 02:48:06
LinkedList
,而不是使用迭代器从列表中间进行繁重的删除。适当的基准:
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
@OperationsPerInvocation(StreamVsVanilla.N)
public class StreamVsVanilla {
public static final int N = 10000;
static List<Integer> sourceList = new ArrayList<>();
static {
for (int i = 0; i < N; i++) {
sourceList.add(i);
}
}
@Benchmark
public List<Double> vanilla() {
List<Double> result = new ArrayList<>(sourceList.size() / 2 + 1);
for (Integer i : sourceList) {
if (i % 2 == 0){
result.add(Math.sqrt(i));
}
}
return result;
}
@Benchmark
public List<Double> stream() {
return sourceList.stream()
.filter(i -> i % 2 == 0)
.map(Math::sqrt)
.collect(Collectors.toCollection(
() -> new ArrayList<>(sourceList.size() / 2 + 1)));
}
}
结果:
Benchmark Mode Samples Mean Mean error Units
StreamVsVanilla.stream avgt 10 17.588 0.230 ns/op
StreamVsVanilla.vanilla avgt 10 10.796 0.063 ns/op
正如我所期望的那样,流实现相当慢。JIT能够内联所有lambda的东西,但不能生成像普通版本那样完美简洁的代码。
一般来说,Java 8流并不神奇。他们不能加速已经很好实现的东西(可能是使用普通的迭代或Java5的for-each语句替换为Iterable.forEach()
和Collection.removeIf()
调用)。Streams更多的是关于编码的便利性和安全性。便利性--速度权衡在这里起作用。
发布于 2014-03-26 19:43:46
1)使用您的基准测试,您看到的时间少于1秒。这意味着副作用会对你的结果产生很大的影响。所以,我把你的任务增加了10倍
int max = 10_000_000;
并运行了你的基准测试。我的结果:
Collections: Elapsed time: 8592999350 ns (8.592999 seconds)
Streams: Elapsed time: 2068208058 ns (2.068208 seconds)
Parallel streams: Elapsed time: 7186967071 ns (7.186967 seconds)
没有编辑(int max = 1_000_000
)的结果是
Collections: Elapsed time: 113373057 ns (0.113373 seconds)
Streams: Elapsed time: 135570440 ns (0.135570 seconds)
Parallel streams: Elapsed time: 104091980 ns (0.104092 seconds)
这就像你的结果:流比收集慢。结论:在流初始化/值传输上花费了大量时间。
2)增加任务流之后,任务流变得更快(这没问题),但并行流仍然太慢。怎么了?注意:您的命令中包含collect(Collectors.toList())
。在并发执行的情况下,收集到单个收集本质上引入了性能瓶颈和开销。可以通过替换来估计开销的相对成本
collecting to collection -> counting the element count
对于streams,这可以由collect(Collectors.counting())
完成。我得到了结果:
Collections: Elapsed time: 41856183 ns (0.041856 seconds)
Streams: Elapsed time: 546590322 ns (0.546590 seconds)
Parallel streams: Elapsed time: 1540051478 ns (1.540051 seconds)
这是一个大任务!(int max = 10000000
) 结论:收集项目收集大部分时间。最慢的部分是添加到列表中。顺便说一句,Collectors.toList()
使用简单的ArrayList
。
发布于 2015-11-20 03:31:13
public static void main(String[] args) {
//Calculating square root of even numbers from 1 to N
int min = 1;
int max = 10000000;
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( doSomeCalculate(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 -> doSomeCalculate(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 -> doSomeCalculate(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));
}
static double doSomeCalculate(int input) {
for(int i=0; i<100000; i++){
Math.sqrt(i+input);
}
return Math.sqrt(input);
}
我稍微修改了一下代码,在我的mac book pro上运行,它有8个内核,我得到了一个合理的结果:
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)
https://stackoverflow.com/questions/22658322
复制相似问题