针对不变性的Scala编译器优化



scala编译器是否通过删除块中只使用一次的val的引用来优化内存使用?

想象一下,一个对象聚合了一些巨大的数据,达到了克隆数据或其派生数据可能会占用JVM/机器最大内存量的大小。

一个最小的代码示例,但想象一个更长的数据转换链:

val huge: HugeObjectType
val derivative1 = huge.map(_.x)
val derivative2 = derivative1.groupBy(....)

在计算出derivative1之后,编译器是否会将huge标记为符合垃圾收集条件?或者它会一直保持它的活力直到包装块退出?

从理论上讲,不变性很好,我个人觉得它让人上瘾。但是,为了适合那些在当前操作系统上无法逐项流式处理的大数据对象,我会声称它与合理的内存利用率内在地阻抗不匹配,因为JVM上的大数据应用程序不是吗,除非编译器针对这种情况进行优化。。

首先:只要JVM GC认为有必要,就会实际释放未使用的内存。所以scalac对此无能为力。

scalac能做的唯一一件事是将引用设置为null,不仅当它们超出范围时,而且当它们不再使用时。

基本上

val huge: HugeObjectType
val derivative1 = huge.map(_.x)
huge = null // inserted by scalac
val derivative2 = derivative1.groupBy(....)
derivative1 = null // inserted by scalac

根据scala内部的这个线程,它目前做而不是这样做,最新的热点JVM也不提供补救。请参阅scalac黑客Grzegorz Kossakowski的帖子和其他帖子。

对于JVM JIT编译器正在优化的方法,JIT编译器将尽快为空引用。然而,对于只执行一次的主方法,JVM永远不会尝试完全优化它

上面链接的线程包含了对该主题和所有权衡的非常详细的讨论。

请注意,在典型的大数据计算框架(如apachespark)中,使用的值不是对数据的直接引用。因此,在这些框架中,引用的生存期通常不是问题。

对于上面给出的示例,所有中间值都只使用一次。因此,一个简单的解决方案是将所有中间结果定义为def。

def huge: HugeObjectType
def derivative1 = huge.map(_.x)
def derivative2 = derivative1.groupBy(....)
val result = derivative2.<some other transform>

一种不同但非常有效的方法是使用迭代器!通过迭代器将mapfilter等函数链接起来,逐项处理它们,从而不会实现任何中间集合。。这非常符合场景!这对于像CCD_ 6这样的函数没有帮助,但是可以显著减少用于前一个函数和类似函数的存储器分配。上面提到的归功于Simon Schafer。

derivative1一旦超出范围(并且没有其他引用),就会被垃圾收集。为了确保尽快实现,请执行以下操作:

val huge: HugeObjectType
val derivative2 = {
    val derivative1 = huge.map(_.x)
    derivative1.groupBy(....)
}

从代码可读性的角度来看,这也更好,因为很明显,derivative1存在的唯一原因derivative2,并且在右括号之后不再使用它。

相关内容

  • 没有找到相关文章

最新更新