Scala 编译器是否尝试减少多个顺序映射和平面映射之间的对象创建

Does the Scala compiler try to reduce object creation between several sequential maps and flatmaps

这是一个关于 Scala 编译器的问题。

假设我有一个列表,我通过几个地图和平面地图转换该列表。

val myList = List(1,2,3,4,5)

val transformed = myList.map(_+1).map(_*2).flatmap(x=>List(x-1,x,x+1))

比方说我再改造一下。

val moreTransformed = transformed.map(_-2).map(_/5).flatMap(x=>List(x/2,x*2))

我的问题可以分为两部分

  1. 在为 transformed 生成 val 时,底层 Java 字节码是否会创建中间列表?我指的是在 transformed 的计算中对 map 和 flatMap 的连续调用。 Scala 编译器可以将这些调用组合成一个 flatMap 吗?如果我在一个对象列表上操作,这将需要创建更少的中间对象。如果编译器很天真,只是简单地创建中间对象,则可能会导致涉及长链 map 和 flatMap 的计算产生相当大的开销。

  2. 假设在上面创建的两个 val 中,我在进一步计算中只使用 moreTransformed(第二个 val)。也就是说,我只在 moreTransformed 的计算中使用 transformed (第一个 val),其他地方都没有。 Scala 编译器是否足够聪明,不会为 transformed 创建列表而只计算 moreTransformed?将 transformedmoreTransformed 中的所有函数组合起来,只生成一个列表,即 moreTransformed 的值,是否足够聪明?

我不确定,编译器生成什么样的字节码。我会尝试用 概念

的术语来回答它

If I was operating on a list of objects, this would entail creation of fewer intermediate objects. If the compiler is naive and simply creates the intermediate objects, that could potentially result in considerable overhead for computations that involve long chains of map and flatMap.

是的。 Scala 的集合 List 默认是 strict,这意味着所有需要的中间对象都会被计算和生成。

Is the Scala compiler smart enough not to create the List for transformed and to compute only moreTransformed?

简答,否

Is it smart enough to compose all the functions in transformed and moreTransformed so that only a single List, the value of moreTransformed, is produced?

没有

如果您想要第 2 和第 3 块中提到的功能,则有 LazyListStream API。一般来说,惰性集合对于描述连续的转换操作特别有用,无需评估中间转换。

您可以阅读有关 strictlazy 评估的简要概述 here

lazy evaluationhere

上的一些练习

不管编译器有多聪明,它仍然必须符合语言指定的内容

在这种情况下,语言表示每个 map/flatMap 操作必须 出现 才能在下一个操作开始之前完成。因此,如果编译器可以保证行为相同,则它只能执行您提到的优化。

在问题中的例子的具体情况下,编译器知道myList中的内容,并且所应用的函数具有非常清晰的语义。编译器 可以 理论上优化它并预先计算结果,而无需在 运行 时间做任何事情。

在更一般的情况下,编译器将不知道 myList 中的内容,并且操作可能会失败。在这种情况下,编译器别无选择,只能依次执行每个操作。只有这样才能根据语言保证正确的结果。


请注意,Scala 代码通常在带有 JIT 编译器的 JVM 中执行,这是完成大部分优化的地方。顺序 map 调用将转换为字节码中的顺序循环,在某些情况下,JIT 编译器可能能够将这些循环组合成一个循环。但是,如果循环中有任何副作用(包括对象分配),则无法进行此优化。