在mapToInt之后调用map是否有任何优势,如果需要的话

我试图计算列表中值的平方和。 以下是三种变体,它们都计算出所需的值。 我想知道哪一个最有效率。 我期待第三个更高效,因为自动装箱只进行一次。

// sum of squares int sum = list.stream().map(x -> x * x).reduce((x, y) -> x + y).get(); System.out.println("sum of squares: " + sum); sum = list.stream().mapToInt(x -> x * x).sum(); System.out.println("sum of squares: " + sum); sum = list.stream().mapToInt(x -> x).map(x -> x * x).sum(); System.out.println("sum of squares: " + sum); 

如有疑问,请测试! 使用jmh,我在100k元素列表中得到以下结果(以微秒为单位,越小越好):

 Benchmark Mode Samples Score Error Units capSO32462798.for_loop avgt 10 119.110 0.921 us/op capSO32462798.mapToInt avgt 10 129.702 1.040 us/op capSO32462798.mapToInt_map avgt 10 129.753 1.516 us/op capSO32462798.map_reduce avgt 10 1262.802 12.197 us/op capSO32462798.summingInt avgt 10 134.821 1.203 us/op 

所以你有,从快到慢:

  • for(int i : list) sum += i*i;
  • mapToInt(x -> x * x).sum()mapToInt(x -> x).map(x -> x * x).sum()
  • collect(Collectors.summingInt(x -> x * x))
  • map(x -> x * x).reduce((x, y) -> x + y).get()

请注意,结果非常依赖于JIT优化。 如果映射中的逻辑更复杂,则某些优化可能不可用(更长的代码=更少的内联),在这种情况下,流版本可能比for循环花费多4-5倍的时间 – 但如果该逻辑是CPU重的话差异将再次减少。 分析您的实际应用程序将为您提供更多信息。


基准代码供参考:

 @State(Scope.Benchmark) @BenchmarkMode(Mode.AverageTime) public class SO32462798 { List list; @Setup public void setup() { list = new Random().ints(100_000).boxed().collect(toList()); } @Benchmark public int for_loop() { int sum = 0; for (int i : list) sum += i * i; return sum; } @Benchmark public int summingInt() { return list.stream().collect(Collectors.summingInt(x -> x * x)); } @Benchmark public int mapToInt() { return list.stream().mapToInt(x -> x * x).sum(); } @Benchmark public int mapToInt_map() { return list.stream().mapToInt(x -> x).map(x -> x * x).sum(); } @Benchmark public int map_reduce() { return list.stream().map(x -> x * x).reduce((x, y) -> x + y).get(); } } 

我希望第二个是最快的。

第二个和第三个例子都没有装箱(如果列表中已包含已装箱的元素)。 但是,有拆箱。

您的第二个示例可能有两个拆箱(一个用于x*x每个x*x ),而第三个示例仅拆箱一次。 但是,取消装箱很快,我认为不值得优化,因为带有附加function调用的较长管道肯定会减慢速度。

旁注:一般来说,您不应期望Stream s比数组或列表上的常规迭代更快。 在进行数学计算时,速度很重要(比如这样),最好采用另一种方式:简单地遍历元素。 如果输出是聚合值,则聚合它,如果是映射,则分配新数组或相同大小的列表,并用计算值填充它。