在mapToInt之后调用map有什么好处吗?在需要的地方

2022-09-04 21:06:13

我正在尝试计算列表中值的平方和。以下是三种变体,它们都计算所需的值。我想知道哪一个是最有效的。我期望第三个更有效率,因为自动拳击只完成一次。

    // 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);

答案 1

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

Benchmark                        Mode  Samples     Score    Error  Units
c.a.p.SO32462798.for_loop        avgt       10   119.110    0.921  us/op
c.a.p.SO32462798.mapToInt        avgt       10   129.702    1.040  us/op
c.a.p.SO32462798.mapToInt_map    avgt       10   129.753    1.516  us/op
c.a.p.SO32462798.map_reduce      avgt       10  1262.802   12.197  us/op
c.a.p.SO32462798.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<Integer> 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();
  }
}

答案 2

我希望第二个是最快的。

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

第二个示例可能有两个取消装箱(每个 in 一个),而第三个示例仅取消装箱一次。但是,取消装箱速度很快,我认为不值得对其进行优化,因为带有附加函数调用的更长管道肯定会减慢速度。xx*x

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


推荐