为什么对此Scala代码进行少量更改会对性能产生如此巨大的影响?

tim*_*day 57 optimization performance scala

我正在使用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)
  }
}
Run Code Online (Sandbox Code Playgroud)

但是,如果我将val es=一行向下移动并在范围内进行微不足道的改变main,那么它仅在1秒内运行,这更像我期望看到的并且与等效C++的性能相当.有趣的是,离开val es=原来的地方但同样合格lazy也具有相同的加速效果.

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

Rex*_*err 51

JVM不会将静态初始化程序(这就是它)优化到优化方法调用的同一级别.不幸的是,当你在那里做很多工作时,这会伤害性能 - 这就是一个很好的例子.这也是为什么旧Application特性被认为是有问题的一个原因,以及为什么在Scala 2.9中有一个DelayedInit特性可以获得一些编译器帮助将东西从初始化器转移到稍后调用的方法中.


(编辑:将"构造函数"修复为"初始化程序".相当冗长的拼写!)

  • Scala具体或一般是真的吗?因为我不明白为什么JIT不能在普通的java代码中优化长静态初始化器(或更准确地说它为什么它会区分静态代码块和正常) (2认同)
  • @notnoop.另一方面,静态初始化程序实际上只能运行一次(每个ClassLoader)并具有非平凡的语义:检查JLS 12.4.2.JVM设计人员基本上认为这不值得付出努力.另一方面,主要方法需要进行大量优化才能使代码更快.另外,`main`方法就像任何其他静态方法一样,实际上可以运行多次(你实际上可以自己调用main方法..令人震惊!). (2认同)

Dav*_*ith 40

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

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

HotSpot JVM不会对静态初始化期间遇到的代码执行任何优化,在合理的启发式下,此类代码只运行一次.

  • 我认为这是非常有价值的信息. (5认同)
  • 因为它只影响静态初始化器,所以它只影响Scala单例对象,而不影响类对象 (5认同)
  • 特别是在不变性的情况下,我怀疑它是在类的初始化阶段内进行大量计算的常见模式. (4认同)
  • @ziggystar - 这特别影响_static_初始值设定项. (4认同)