Java 8:Streams vs Collections的性能

我是Java 8的新手。我仍然不深入了解API,但我已经做了一个小的非正式基准测试来比较新Streams API与优秀旧Collections的性能。


测试包括过滤一个列表Integer,并为每个偶数计算平方根并将其存储在结果List中Double。


这是代码:


    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)

对于这个特定的测试,流的速度大约是集合的两倍,并行性没有帮助(或者我使用错误的方式?)。


问题:


这个测试公平吗?我犯了什么错吗?

流比收集慢吗?有谁在这方面做了一个很好的正式基准?

我应该采取哪种方法?

眼眸繁星
浏览 738回答 3
3回答

猛跑小猪

LinkedList使用迭代器停止使用除列表中间的大量删除之外的任何内容。停止手动编写基准测试代码,使用JMH。适当的基准:@OutputTimeUnit(TimeUnit.NANOSECONDS)@BenchmarkMode(Mode.AverageTime)@OperationsPerInvocation(StreamVsVanilla.N)public class StreamVsVanilla {&nbsp; &nbsp; public static final int N = 10000;&nbsp; &nbsp; static List<Integer> sourceList = new ArrayList<>();&nbsp; &nbsp; static {&nbsp; &nbsp; &nbsp; &nbsp; for (int i = 0; i < N; i++) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sourceList.add(i);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; @Benchmark&nbsp; &nbsp; public List<Double> vanilla() {&nbsp; &nbsp; &nbsp; &nbsp; List<Double> result = new ArrayList<>(sourceList.size() / 2 + 1);&nbsp; &nbsp; &nbsp; &nbsp; for (Integer i : sourceList) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (i % 2 == 0){&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result.add(Math.sqrt(i));&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; return result;&nbsp; &nbsp; }&nbsp; &nbsp; @Benchmark&nbsp; &nbsp; public List<Double> stream() {&nbsp; &nbsp; &nbsp; &nbsp; return sourceList.stream()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .filter(i -> i % 2 == 0)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .map(Math::sqrt)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .collect(Collectors.toCollection(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; () -> new ArrayList<>(sourceList.size() / 2 + 1)));&nbsp; &nbsp; }}结果:Benchmark&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Mode&nbsp; &nbsp;Samples&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Mean&nbsp; &nbsp;Mean error&nbsp; &nbsp; UnitsStreamVsVanilla.stream&nbsp; &nbsp; &nbsp; avgt&nbsp; &nbsp; &nbsp; &nbsp; 10&nbsp; &nbsp; &nbsp; &nbsp;17.588&nbsp; &nbsp; &nbsp; &nbsp; 0.230&nbsp; &nbsp; ns/opStreamVsVanilla.vanilla&nbsp; &nbsp; &nbsp;avgt&nbsp; &nbsp; &nbsp; &nbsp; 10&nbsp; &nbsp; &nbsp; &nbsp;10.796&nbsp; &nbsp; &nbsp; &nbsp; 0.063&nbsp; &nbsp; ns/op正如我预期的那样,流实现速度相当慢。JIT能够内联所有lambda内容,但不会像vanilla版本那样生成完美简洁的代码。通常,Java 8流不是魔术。他们无法加速已经很好实现的东西(可能是普通的迭代或Java 5的 - 每个语句都替换为Iterable.forEach()和Collection.removeIf()调用)。流更多的是编码方便性和安全性。方便 - 速度权衡在这里工作。

SMILET

1)您使用基准测试时间不到1秒。这意味着副作用会对您的结果产生强烈影响。所以,我增加了你的任务10次&nbsp; &nbsp; int max = 10_000_000;并运行你的基准。我的结果:Collections: Elapsed time:&nbsp; &nbsp;8592999350 ns&nbsp; (8.592999 seconds)Streams: Elapsed time:&nbsp; &nbsp; &nbsp; &nbsp;2068208058 ns&nbsp; (2.068208 seconds)Parallel streams: Elapsed time:&nbsp; 7186967071 ns&nbsp; (7.186967 seconds)没有edit(int max = 1_000_000)结果Collections: Elapsed time:&nbsp; &nbsp;113373057 ns&nbsp; &nbsp;(0.113373 seconds)Streams: Elapsed time:&nbsp; &nbsp; &nbsp; &nbsp;135570440 ns&nbsp; &nbsp;(0.135570 seconds)Parallel streams: Elapsed time:&nbsp; 104091980 ns&nbsp; &nbsp;(0.104092 seconds)这就像你的结果:流比收集慢。结论:流初始化/值传输花费了大量时间。2)增加任务流后变得更快(没关系),但并行流仍然太慢。怎么了?注意:你有collect(Collectors.toList())命令。收集单个集合实质上会在并发执行时引入性能瓶颈和开销。可以通过替换来估计开销的相对成本collecting to collection -> counting the element count对于流,它可以通过collect(Collectors.counting())。我得到了结果:Collections: Elapsed time:&nbsp; &nbsp;41856183 ns&nbsp; &nbsp; (0.041856 seconds)Streams: Elapsed time:&nbsp; &nbsp; &nbsp; &nbsp;546590322 ns&nbsp; &nbsp;(0.546590 seconds)Parallel streams: Elapsed time:&nbsp; 1540051478 ns&nbsp; (1.540051 seconds)那是一项艰巨的任务!(int max = 10000000)结论:收集物品需要花费大部分时间。最慢的部分是添加到列表中。BTW,简单ArrayList用于Collectors.toList()。

繁星coding

public static void main(String[] args) {&nbsp; &nbsp; //Calculating square root of even numbers from 1 to N&nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; int min = 1;&nbsp; &nbsp; int max = 10000000;&nbsp; &nbsp; List<Integer> sourceList = new ArrayList<>();&nbsp; &nbsp; for (int i = min; i < max; i++) {&nbsp; &nbsp; &nbsp; &nbsp; sourceList.add(i);&nbsp; &nbsp; }&nbsp; &nbsp; List<Double> result = new LinkedList<>();&nbsp; &nbsp; //Collections approach&nbsp; &nbsp; long t0 = System.nanoTime();&nbsp; &nbsp; long elapsed = 0;&nbsp; &nbsp; for (Integer i : sourceList) {&nbsp; &nbsp; &nbsp; &nbsp; if(i % 2 == 0){&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result.add( doSomeCalculate(i));&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; elapsed = System.nanoTime() - t0;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; System.out.printf("Collections: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));&nbsp; &nbsp; //Stream approach&nbsp; &nbsp; Stream<Integer> stream = sourceList.stream();&nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; t0 = System.nanoTime();&nbsp; &nbsp; result = stream.filter(i -> i%2 == 0).map(i -> doSomeCalculate(i))&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .collect(Collectors.toList());&nbsp; &nbsp; elapsed = System.nanoTime() - t0;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; System.out.printf("Streams: Elapsed time:\t\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));&nbsp; &nbsp; //Parallel stream approach&nbsp; &nbsp; stream = sourceList.stream().parallel();&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;&nbsp; &nbsp; t0 = System.nanoTime();&nbsp; &nbsp; result = stream.filter(i -> i%2 == 0).map(i ->&nbsp; doSomeCalculate(i))&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .collect(Collectors.toList());&nbsp; &nbsp; elapsed = System.nanoTime() - t0;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; System.out.printf("Parallel streams: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));&nbsp; &nbsp; &nbsp;&nbsp;}static double doSomeCalculate(int input) {&nbsp; &nbsp; for(int i=0; i<100000; i++){&nbsp; &nbsp; &nbsp; &nbsp; Math.sqrt(i+input);&nbsp; &nbsp; }&nbsp; &nbsp; return Math.sqrt(input);}我稍微更改了代码,在我的mac book pro上运行了8个内核,得到了一个合理的结果:收藏:经历时间:1522036826 ns(1.522037秒)流:经过的时间:4315833719 ns(4.315834秒)并行流:经过时间:261152901 ns(0.261153秒)
打开App,查看更多内容
随时随地看视频慕课网APP