为了账号安全,请及时绑定邮箱和手机立即绑定

Java 8:Streams vs Collections的性能

Java 8:Streams vs Collections的性能

眼眸繁星 2019-08-30 15:53:40
我是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)对于这个特定的测试,流的速度大约是集合的两倍,并行性没有帮助(或者我使用错误的方式?)。问题:这个测试公平吗?我犯了什么错吗?流比收集慢吗?有谁在这方面做了一个很好的正式基准?我应该采取哪种方法?
查看完整描述

3 回答

?
猛跑小猪

TA贡献1858条经验 获得超8个赞

LinkedList使用迭代器停止使用除列表中间的大量删除之外的任何内容。


停止手动编写基准测试代码,使用JMH。


适当的基准:


@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内容,但不会像vanilla版本那样生成完美简洁的代码。


通常,Java 8流不是魔术。他们无法加速已经很好实现的东西(可能是普通的迭代或Java 5的 - 每个语句都替换为Iterable.forEach()和Collection.removeIf()调用)。流更多的是编码方便性和安全性。方便 - 速度权衡在这里工作。


查看完整回答
反对 回复 2019-08-30
?
SMILET

TA贡献1796条经验 获得超4个赞

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)

没有edit(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

对于流,它可以通过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)结论:收集物品需要花费大部分时间。最慢的部分是添加到列表中。BTW,简单ArrayList用于Collectors.toList()。


查看完整回答
反对 回复 2019-08-30
?
繁星coding

TA贡献1797条经验 获得超4个赞

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个内核,得到了一个合理的结果:


收藏:经历时间:1522036826 ns(1.522037秒)


流:经过的时间:4315833719 ns(4.315834秒)


并行流:经过时间:261152901 ns(0.261153秒)


查看完整回答
反对 回复 2019-08-30
  • 3 回答
  • 0 关注
  • 735 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信