【问题标题】:Why does a small change to this Scala code make such a huge difference to performance?为什么这个 Scala 代码的一个小改动会对性能产生如此巨大的影响?
【发布时间】:2011-09-10 02:05:14
【问题描述】:

我在 32 位 Debian 6.0 (Squeeze) 系统(2.5 GHz Core 2 CPU)上运行 sun-java6 6.24-1,但使用 Wheezy 的 Scala 2.8.1 包。

这段代码使用 scalac -optimise 编译,运行时间超过 30 秒:

object Performance {

  import scala.annotation.tailrec

  @tailrec def gcd(x:Int,y:Int):Int = {
    if (x == 0)
      y 
    else 
      gcd(y%x,x)
  }

  val p = 1009
  val q = 3643
  val t = (p-1)*(q-1)

  val es = (2 until t).filter(gcd(_,t) == 1)
  def main(args:Array[String]) {
    println(es.length)
  }
}

但是,如果我将val es= 向下移动一行并在main 的范围内进行微不足道的更改,那么它只需 1 秒即可运行,这更像是我所期望看到的并且与等效 C++ 的性能。有趣的是,将val es= 保留在原处,但使用lazy 对其进行限定也具有相同的加速效果。

这里发生了什么?为什么在函数范围外执行计算这么慢?

【问题讨论】:

  • 我在没有-optimize, FWIW 的 2.8.1 (Sun Java 1.6.0_24-b07) 中看到了同样的情况。
  • 一提到优化就有点牵强,抱歉;我从来没有真正看到使用它(或没有)对我从事的任何 Scala 产品的性能产生任何重大影响。
  • 有趣...同样的事情发生在带有和不带 -optimize 的 scala 2.9.0 的类似系统上。

标签: performance optimization scala


【解决方案1】:

JVM 不会将静态初始化程序(就是这样)优化到与优化方法调用相同的水平。不幸的是,当你在那里做很多工作时,这会损害性能——这是一个很好的例子。这也是为什么旧的Application trait 被认为是有问题的原因之一,以及为什么在 Scala 2.9 中有一个 DelayedInit trait 可以在编译器的帮助下将内容从初始化程序移动到稍后调用的方法中。


(编辑:将“constructor”固定为“initializer”。相当冗长的错字!)

【讨论】:

  • 这是 Scala 特有的还是普遍正确的?因为我不明白为什么 JIT 不会优化普通 java 代码中的长静态初始化程序(或者更确切地说,为什么它会区分静态代码块和普通代码块)
  • @Voo,这通常是正确的,甚至影响 Java 静态代码。好像是JVM设计的;一种可能的假设:鉴于静态初始化程序只运行一次,JVM 作者不想投入太多时间来优化他们的用例。
  • @timday - 为每一个昂贵的计算添加惰性会在一段时间后变得很累。很高兴有一种机制可以一次完成所有操作。
  • @notnoop 我不明白为什么。当然,静态初始化程序没有优化——就像任何只调用一次的主方法都不会优化一样。但是如果我们内部有一些复杂的计算,为什么不能像往常一样按照通常的规则进行 JITed 呢?
  • @notnoop。另一方面,静态初始化器实际上只能运行一次(每个 ClassLoader)并且具有非平凡的语义:检查 JLS 12.4.2。 JVM 设计者基本上认为这不值得。另一个主要方法需要大量优化才能真正使代码更快。此外,main 方法就像任何其他静态方法一样实际上可以运行多次(您实际上可以自己调用 main 方法......令人震惊!)。
【解决方案2】:

顶级对象块内的代码被转换为对象类的静态初始化程序。 Java 中的等价物是

class Performance{
    static{
      //expensive calculation
    }
    public static void main(String[] args){
      //use result of expensive calculation
    }
}

HotSpot JVM 不会对静态初始化程序期间遇到的代码执行任何优化,因为这样的代码只会运行一次。

【讨论】:

  • 是的,这很吸引人。我是从 90 年代中期最后一次接触 Java 的人的角度来看的,但由于 Scala 是一种比 Java 更具吸引力的语言(无论如何对我来说),它被引诱回到 JVM。因此,当底层技术的现实被这样的事情意外揭示时,我确实会感到非常意外。
  • 尤其是不变性,我怀疑在类的初始化阶段进行大量计算是一种常见的模式。
  • @ziggystar - 这特别影响 static 初始化器。
  • 而且由于它只影响静态初始化器,它只影响 Scala 单例对象,而不影响类对象
猜你喜欢
  • 2015-10-13
  • 1970-01-01
  • 2014-07-02
  • 2019-06-24
  • 1970-01-01
  • 2011-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多