【问题标题】:Does the Scala compiler try to reduce object creation between several sequential maps and flatmapsScala 编译器是否尝试减少几个顺序映射和平面映射之间的对象创建
【发布时间】:2019-12-23 06:49:39
【问题描述】:

这是一个关于 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 中的所有函数是否足够聪明,从而只生成一个List,即moreTransformed 的值?

【问题讨论】:

  • 除了已经告诉过的内容。你不应该真正关心这个。 - 除非,您已经进行了基准测试并将其识别为瓶颈。我就是这种情况,可以使用许多优化。 - 对于第一个示例,以.iterator 开始链并以`toList, that way it will be just one iteration. - for the second case, consider a **LazyList** - Finally, if all your program is a big data pipeline, consider using **Streaming** _(AkkaStreams, fs2, monix.Observable` 或zio.ZStream)_ 结束链,或者如果数据太大,请考虑@ 987654338@ 或 Flink.

标签: scala monads functor scalac scala-compiler


【解决方案1】:

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

的方式来回答它

如果我在对象列表上进行操作,这将需要创建更少的中间对象。如果编译器很幼稚,只是简单地创建中间对象,则可能会导致涉及长链 map 和 flatMap 的计算产生相当大的开销。

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

Scala 编译器是否足够聪明,不会为转换创建列表而只计算 moreTransformed?

简短回答,否

将所有的函数组合在transformed和moreTransformed中是否足够聪明,从而只产生一个List,即moreTransformed的值?

没有

如果您想要第 2 和第 3 块中提到的功能,可以使用 LazyListStream API。通常惰性集合对于描述连续转换操作而不评估中间转换特别有用。

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

lazy evaluationhere上做一些练习

【讨论】:

    【解决方案2】:

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

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

    在问题示例的具体情况下,编译器知道myList里面是什么,应用的函数语义非常清晰。编译器可以理论上优化它并预先计算结果,而无需在运行时执行任何操作。

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


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

    【讨论】:

    • 请注意,即使在具有更多限制性语义(ergo,编译器的更多信息)以及更多“research-y”编译器的 Haskell 中,map fusion 实际上也是至少在 GHC 中手动实现的优化:downloads.haskell.org/~ghc/latest/docs/html/users_guide/… Rewrite Rules 是您添加到源代码中的规则,它们充当编译器的编译指示并告诉它“如果你看到 this i> 源代码模式,您实际上可以将其替换为 that 模式”。所以,IOW,编译器本身不够聪明,无法进行这种优化,……
    • … 而是在标准库map 实现的源代码中有一个编译指示告诉编译器(map f) . (map g)map (f . g)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-03-04
    • 1970-01-01
    • 2019-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多