注意:尽管我将使用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
提前致谢
答案 0 :(得分:7)
通过使用foldLeft
来获得更好的表现,val computation = range.foldLeft(0.0){
case (sum,i) => sum + 4.0 * (1 - (i % 2) * 2) / (2 * i + 1))
}
会在一次通过中计算总和作为您所示的必要示例:
{{1}}
但是,它可能仍然比它的命令对手慢。如果确实是您的应用程序的瓶颈,您应该使用最快的版本(带有while循环的命令式版本)。它对Actors是安全的,因为可变状态将是完全孤立的。
在所有其他情况下,最好使用凌乱的代码丢失几个百分比的性能(在整个应用程序中测量)。
答案 1 :(得分:2)
我认为你的程序中的问题是range.map(..)
方法的调用,它是 strict (非懒惰)。这意味着 - 如果您的范围有1000个元素,map
方法将为1000个新元素分配内存并立即计算它们。
所以我想改进代码的另一种非常简单的方法是使用范围视图:range.view.map(..)
,这样就可以懒惰地计算映射元素并分配恒定的内存量。
答案 2 :(得分: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)}
}
我猜这会避免需要中间集合,因此可能会运行得更快。