Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/performance/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java8:流与集合的性能_Java_Performance_Collections_Java 8_Java Stream - Fatal编程技术网

Java8:流与集合的性能

Java8:流与集合的性能,java,performance,collections,java-8,java-stream,Java,Performance,Collections,Java 8,Java Stream,我不熟悉Java8。我仍然不太了解API,但我已经做了一个非正式的小基准测试,比较了新Streams API和旧集合的性能 该测试包括过滤一个整数列表,对于每个偶数,计算平方根并将其存储在双精度的列表中 代码如下: public static void main(String[] args) { //Calculating square root of even numbers from 1 to N int min = 1;

我不熟悉Java8。我仍然不太了解API,但我已经做了一个非正式的小基准测试,比较了新Streams API和旧集合的性能

该测试包括过滤一个
整数列表
,对于每个偶数,计算平方根并将其存储在
双精度
列表

代码如下:

    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));      
    }.
对于这个特定的测试,流的速度大约是集合速度的两倍,并行性没有帮助(或者我用错了方法?)

问题:

  • 这个测试公平吗?我犯了什么错误吗
  • 流是否比集合慢?有没有人在这方面制定了一个好的正式基准
  • 我应该争取哪种方法

更新结果。

按照@pveentjer的建议,我在JVM预热(1k次迭代)后运行了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)

在这种情况下,流的性能更高。我想知道,如果在运行时只调用一次或两次过滤函数,那么在应用程序中会观察到什么情况。

对于您尝试执行的操作,我不会使用常规java api。正在进行大量装箱/拆箱操作,因此会产生巨大的性能开销

就我个人而言,我认为很多设计的API都是垃圾,因为它们创建了大量的对象垃圾

尝试使用double/int的基本数组,尝试使用单线程,看看性能如何

附言:
您可能想看看JMH,以处理基准测试。它处理了一些典型的陷阱,比如预热JVM

对于您正在尝试做的事情,无论如何,我不会使用常规java api。正在进行大量装箱/拆箱操作,因此会产生巨大的性能开销

就我个人而言,我认为很多设计的API都是垃圾,因为它们创建了大量的对象垃圾

尝试使用double/int的基本数组,尝试使用单线程,看看性能如何

附言: 您可能想看看JMH,以处理基准测试。它处理了一些典型的陷阱,比如预热JVM

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
对于流,可以通过
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
结论:收集要收集的项目花费了大部分时间。最慢的部分是添加到列表中。顺便说一句,simple
ArrayList
用于
收集器。toList()

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
对于流,可以通过
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
结论:收集要收集的项目花费了大部分时间。最慢的部分是添加到列表中。顺便说一句,simple
ArrayList
用于
collector.toList()

  • 停止使用
    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)));
        }
    }
    
    正如我所预期的,流实现相当慢。JIT能够内联所有lambda内容,但不能产生像普通版本那样完美简洁的代码

    一般来说,Java8流不是神奇的。它们无法加速已经很好地实现的事情(可能是用
    Iterable.forEach()
    Collection.removeIf()
    调用替换每个语句的普通迭代或Java 5)。流更多的是关于编码的方便性和安全性。便利性——速度权衡在这里起作用

        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);
    }
    
  • 停止使用
    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)));
        }
    }
    
    正如我所预期的,流实现相当慢。JIT能够内联所有lambda内容,但不能产生像普通版本那样完美简洁的代码

    一般来说,Java8流不是神奇的。它们无法加速已经很好地实现的事情(可能是用
    Iterable.forEach()
    Collection.removeIf()
    调用替换每个语句的普通迭代或Java 5)。溪流