经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
为什么list.sort()比Stream().sorted()更快?
来源:cnblogs  作者:是奉壹呀  时间:2023/8/2 9:18:12  对本文有异议

昨天写了一篇文章《小细节,大问题。分享一次代码优化的过程》,里面提到了list.sort()和list.strem().sorted()排序的差异。
说到list sort()排序比stream().sorted()排序性能更好。
但没说到为什么。
有朋友也提到了这一点。

本文重新开始,先问是不是,再问为什么。


真的更好吗?


先简单写个demo

  1. List<Integer> userList = new ArrayList<>();
  2. Random rand = new Random();
  3. for (int i = 0; i < 10000 ; i++) {
  4. userList.add(rand.nextInt(1000));
  5. }
  6. List<Integer> userList2 = new ArrayList<>();
  7. userList2.addAll(userList);
  8. Long startTime1 = System.currentTimeMillis();
  9. userList2.stream().sorted(Comparator.comparing(Integer::intValue)).collect(Collectors.toList());
  10. System.out.println("stream.sort耗时:"+(System.currentTimeMillis() - startTime1)+"ms");
  11. Long startTime = System.currentTimeMillis();
  12. userList.sort(Comparator.comparing(Integer::intValue));
  13. System.out.println("List.sort()耗时:"+(System.currentTimeMillis()-startTime)+"ms");

输出

  1. stream.sort耗时:62ms
  2. List.sort()耗时:7ms

由此可见list原生排序性能更好。
能证明吗?
证据错了。


再把demo变换一下,先输出stream.sort

  1. List<Integer> userList = new ArrayList<>();
  2. Random rand = new Random();
  3. for (int i = 0; i < 10000 ; i++) {
  4. userList.add(rand.nextInt(1000));
  5. }
  6. List<Integer> userList2 = new ArrayList<>();
  7. userList2.addAll(userList);
  8. Long startTime = System.currentTimeMillis();
  9. userList.sort(Comparator.comparing(Integer::intValue));
  10. System.out.println("List.sort()耗时:"+(System.currentTimeMillis()-startTime)+"ms");
  11. Long startTime1 = System.currentTimeMillis();
  12. userList2.stream().sorted(Comparator.comparing(Integer::intValue)).collect(Collectors.toList());
  13. System.out.println("stream.sort耗时:"+(System.currentTimeMillis() - startTime1)+"ms");

此时输出变成了

  1. List.sort()耗时:68ms
  2. stream.sort耗时:13ms

这能证明上面的结论错误了吗?
都不能。
两种方式都不能证明什么。

使用这种方式在很多场景下是不够的,某些场景下,JVM会对代码进行JIT编译和内联优化。

  1. Long startTime = System.currentTimeMillis();
  2. ...
  3. System.currentTimeMillis() - startTime

此时,代码优化前后执行的结果就会非常大。

  1. 基准测试是指通过设计科学的测试方法、测试工具和测试系统,实现对一类测试对象的某项性能指标进行定量的和可对比的测试。

基准测试使得被测试代码获得足够预热,让被测试代码得到充分的JIT编译和优化。


下面是通过JMH做一下基准测试,分别测试集合大小在100,10000,100000时两种排序方式的性能差异。

  1. import org.openjdk.jmh.annotations.*;
  2. import org.openjdk.jmh.infra.Blackhole;
  3. import org.openjdk.jmh.results.format.ResultFormatType;
  4. import org.openjdk.jmh.runner.Runner;
  5. import org.openjdk.jmh.runner.RunnerException;
  6. import org.openjdk.jmh.runner.options.Options;
  7. import org.openjdk.jmh.runner.options.OptionsBuilder;
  8. import java.util.*;
  9. import java.util.concurrent.ThreadLocalRandom;
  10. import java.util.concurrent.TimeUnit;
  11. import java.util.stream.Collectors;
  12. @BenchmarkMode(Mode.AverageTime)
  13. @OutputTimeUnit(TimeUnit.MICROSECONDS)
  14. @Warmup(iterations = 2, time = 1)
  15. @Measurement(iterations = 5, time = 5)
  16. @Fork(1)
  17. @State(Scope.Thread)
  18. public class SortBenchmark {
  19. @Param(value = {"100", "10000", "100000"})
  20. private int operationSize;
  21. private static List<Integer> arrayList;
  22. public static void main(String[] args) throws RunnerException {
  23. // 启动基准测试
  24. Options opt = new OptionsBuilder()
  25. .include(SortBenchmark.class.getSimpleName())
  26. .result("SortBenchmark.json")
  27. .mode(Mode.All)
  28. .resultFormat(ResultFormatType.JSON)
  29. .build();
  30. new Runner(opt).run();
  31. }
  32. @Setup
  33. public void init() {
  34. arrayList = new ArrayList<>();
  35. Random random = new Random();
  36. for (int i = 0; i < operationSize; i++) {
  37. arrayList.add(random.nextInt(10000));
  38. }
  39. }
  40. @Benchmark
  41. public void sort(Blackhole blackhole) {
  42. arrayList.sort(Comparator.comparing(e -> e));
  43. blackhole.consume(arrayList);
  44. }
  45. @Benchmark
  46. public void streamSorted(Blackhole blackhole) {
  47. arrayList = arrayList.stream().sorted(Comparator.comparing(e -> e)).collect(Collectors.toList());
  48. blackhole.consume(arrayList);
  49. }
  50. }

性能测试结果:

可以看到,list sort()效率确实比stream().sorted()要好。


为什么更好?


流本身的损耗


java的stream让我们可以在应用层就可以高效地实现类似数据库SQL的聚合操作了,它可以让代码更加简洁优雅。

但是,假设我们要对一个list排序,得先把list转成stream流,排序完成后需要将数据收集起来重新形成list,这部份额外的开销有多大呢?

我们可以通过以下代码来进行基准测试

  1. import org.openjdk.jmh.annotations.*;
  2. import org.openjdk.jmh.infra.Blackhole;
  3. import org.openjdk.jmh.results.format.ResultFormatType;
  4. import org.openjdk.jmh.runner.Runner;
  5. import org.openjdk.jmh.runner.RunnerException;
  6. import org.openjdk.jmh.runner.options.Options;
  7. import org.openjdk.jmh.runner.options.OptionsBuilder;
  8. import java.util.ArrayList;
  9. import java.util.Comparator;
  10. import java.util.List;
  11. import java.util.Random;
  12. import java.util.concurrent.TimeUnit;
  13. import java.util.stream.Collectors;
  14. @BenchmarkMode(Mode.AverageTime)
  15. @OutputTimeUnit(TimeUnit.MICROSECONDS)
  16. @Warmup(iterations = 2, time = 1)
  17. @Measurement(iterations = 5, time = 5)
  18. @Fork(1)
  19. @State(Scope.Thread)
  20. public class SortBenchmark3 {
  21. @Param(value = {"100", "10000"})
  22. private int operationSize; // 操作次数
  23. private static List<Integer> arrayList;
  24. public static void main(String[] args) throws RunnerException {
  25. // 启动基准测试
  26. Options opt = new OptionsBuilder()
  27. .include(SortBenchmark3.class.getSimpleName()) // 要导入的测试类
  28. .result("SortBenchmark3.json")
  29. .mode(Mode.All)
  30. .resultFormat(ResultFormatType.JSON)
  31. .build();
  32. new Runner(opt).run(); // 执行测试
  33. }
  34. @Setup
  35. public void init() {
  36. // 启动执行事件
  37. arrayList = new ArrayList<>();
  38. Random random = new Random();
  39. for (int i = 0; i < operationSize; i++) {
  40. arrayList.add(random.nextInt(10000));
  41. }
  42. }
  43. @Benchmark
  44. public void stream(Blackhole blackhole) {
  45. arrayList.stream().collect(Collectors.toList());
  46. blackhole.consume(arrayList);
  47. }
  48. @Benchmark
  49. public void sort(Blackhole blackhole) {
  50. arrayList.stream().sorted(Comparator.comparing(Integer::intValue)).collect(Collectors.toList());
  51. blackhole.consume(arrayList);
  52. }
  53. }

方法stream测试将一个集合转为流再收集回来的耗时。

方法sort测试将一个集合转为流再排序再收集回来的全过程耗时。


测试结果如下:

可以发现,集合转为流再收集回来的过程,肯定会耗时,但是它占全过程的比率并不算高。

因此,这部只能说是小部份的原因。


排序过程


我们可以通过以下源码很直观的看到。

  • 1 begin方法初始化一个数组。
  • 2 accept 接收上游数据。
  • 3 end 方法开始进行排序。
    这里第3步直接调用了原生的排序方法,完成排序后,第4步,遍历向下游发送数据。

所以通过源码,我们也能很明显地看到,stream()排序所需时间肯定是 > 原生排序时间。

只不过,这里要量化地搞明白,到底多出了多少,这里得去编译jdk源码,在第3步前后将时间打印出来。

这一步我就不做了。
感兴趣的朋友可以去测一下。

不过我觉得这两点也能很好地回答,为什么list.sort()比Stream().sorted()更快。

补充说明:

  1. 本文说的stream()流指的是串行流,而不是并行流。
  2. 绝大多数场景下,几百几千几万的数据,开心就好,怎么方便怎么用,没有必要去计较这点性能差异。

原文链接:https://www.cnblogs.com/eryuan/p/17598880.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号