3

注意:尽管我将使用 Akka 教程代码作为测试用例,但我的问题是关于 Scala 的一般不变性性能,以及如何调整 JVM

我正在接近 Akka,我正在尝试它的教程代码Akka getting started;我将教程中的源代码原样粘贴到我自己的项目中并运行它,获得以下输出:

Calculation time:       660 milliseconds

在此之后,我尝试在代码上做一些工作,重写这个函数

def calculatePiFor(start: Int, nrOfElements: Int): Double = {
  var acc = 0.0
  for (i <- start until (start + nrOfElements))
    acc += 4.0 * (1 - (i % 2) * 2) / (2 * i + 1)
  acc
}

不使用var累加器,像这样

def calculatePiFor(start: Int, nrOfElements: Int): Double = {
  val range = start until (start + nrOfElements)
  val computation = range.map(i => 4.0 * (1 - (i % 2) * 2) / (2 * i + 1))
  computation.sum
}

好吧,它起作用了,但性能有点退化

Calculation time:       1737 milliseconds

我的问题来了:我的功能是否有任何重大错误,或者毫无疑问会导致这些表现?如果没有,任何人都可以指出调整 JVM 以提高这些性能的好规则吗?

我在 Scala 2.9 上运行我的代码,使用 sbt 0.12.0 及其默认的 java 选项(捆绑在 sbt.bat 中):

_JAVA_OPTS=-Xmx512M -XX:MaxPermSize=256m -XX:ReservedCodeCacheSize=128m

提前致谢

4

3 回答 3

7

foldLeft通过使用which 将计算总和作为您展示的命令式示例,您将获得更好的性能:

val computation = range.foldLeft(0.0){
  case (sum,i) => sum + 4.0 * (1 - (i % 2) * 2) / (2 * i + 1))
}

但是,它可能仍然比它的命令式对应物慢。如果它确实是您的应用程序的瓶颈,您应该使用最快的版本(带有 while 循环的命令式版本)。它对 Actor 来说是安全的,因为可变状态将被完全隔离。

在所有其他情况下,最好损失百分之几的性能(在整个应用程序上测量时)使用凌乱的代码。

于 2012-11-16T11:04:54.303 回答
2

我认为您程序中的问题是调用严格(非懒惰)的range.map(..)方法。这意味着 - 如果您的范围有 1000 个元素,则方法将为 1000 个新元素分配内存并立即计算它们。map

因此,我猜想改进代码的替代且非常简单的方法是使用范围视图:range.view.map(..),以便延迟计算映射元素并分配恒定数量的内存。

于 2012-11-16T11:31:04.203 回答
1

您可以尝试使用 foldLeft 而不是 map/sum:

  def calculatePiFor(start: Int, nrOfElements: Int): Double = {
    val range = start until (start + nrOfElements)
    range.foldLeft(0.0) { (acc,i) => acc + 4.0 * (1 - (i % 2) * 2) / (2 * i + 1)}
  } 

我猜这将避免对中间集合的需要,因此可能运行得更快。

于 2012-11-16T11:17:04.680 回答