Java 8:流与集合的性能

新手上路,请多包涵

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

测试包括过滤 Integer 的列表,并为每个偶数计算平方根并将其存储在结果 ListDouble .

这是代码:

     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)

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

问题:

  • 这个考试公平吗?我犯了什么错误吗?
  • 流比集合慢吗?有没有人对此做出良好的正式基准?
  • 我应该努力采用哪种方法?

更新结果。

按照@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)

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

原文由 Mister Smith 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 439
1 个回答
  1. 停止使用 LinkedList 除了使用迭代器从列表中间大量删除之外的任何内容。

  2. 停止手动编写基准测试代码,使用 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 的 for-each 语句替换为 Iterable.forEach()Collection.removeIf() 调用)。流更多的是关于编码的便利性和安全性。便利——速度权衡在这里起作用。

原文由 leventov 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏