我目前正在分析用Scala编写的应用程序的性能,我想知道是否可以使用功能构造.一方面,我喜欢功能性编程的优雅和简洁,另一方面,我害怕最终的表现.我发现了一个特别好的例子.
我有一个包含一百万个字符的字符串,我需要对每个数字求和.典型的功能方法是这样的:
val sum = value.map(_.asDigit).sum.toString
Run Code Online (Sandbox Code Playgroud)
然而,这种美观,简洁,功能性的方法需要0.98秒(几乎一秒钟)
var sum = 0;
for(digit <- value)
sum += digit.asDigit
Run Code Online (Sandbox Code Playgroud)
另一方面,这种必要的方法仅需0.022秒(上述时间的2.24%) - 速度提高约50倍......
我确信问题出现了,因为Scala在第一种方法中生成一个新列表,然后再次迭代该列表以创建总和.
依靠功能结构是一个坏主意吗?我的意思是,它们很美 - 我爱它们 - 但它们慢了50倍......
PS我也尝试了别的东西.
val sum = value.foldLeft(0)((sum, value) => sum + value.asDigit)
Run Code Online (Sandbox Code Playgroud)
这种功能性方法虽然简洁,可能比命令式方法更难阅读,但需要0.085秒.它更难阅读,仍然慢4倍......
Rüd*_*ehn 14
首先:你确定你已经对两个版本进行了适当的基准测试吗?仅使用System.nanoTime等测量执行时间不会给出确切的结果.查看JVM性能大师AlekseyShipilёv撰写的这篇有趣且富有洞察力的博客文章.
以下是使用优秀的Thyme scala基准测试库的基准测试:
val value = "1234567890" * 100000
def sumf = value.map(_.asDigit).sum
def sumi = { var sum = 0; for(digit <- value) sum += digit.asDigit; sum }
val th = ichi.bench.Thyme.warmed(verbose = println)
scala> th.pbenchOffWarm("Functional vs. Imperative")(th.Warm(sumf))(th.Warm(sumi))
Benchmark comparison (in 6.654 s): Functional vs. Imperative
Significantly different (p ~= 0)
Time ratio: 0.36877 95% CI 0.36625 - 0.37129 (n=20)
First 40.25 ms 95% CI 40.15 ms - 40.34 ms
Second 14.84 ms 95% CI 14.75 ms - 14.94 ms
res3: Int = 4500000
Run Code Online (Sandbox Code Playgroud)
所以,是的,势在必行的版本是快.但并不像你衡量的那么多.在许多情况下,性能差异将完全无关紧要.对于那些性能差异很重要的情况,scala让您有机会编写命令式代码.总而言之,我认为scala做得很好.
顺便说一下:当你进行适当的基准测试时,你的第二种方法几乎与命令式版本一样快:
def sumf2 = value.foldLeft(0)(_ + _.asDigit)
scala> th.pbenchOffWarm("Functional2 vs. Imperative")(th.Warm(sumf2))(th.Warm(sumi))
Benchmark comparison (in 3.886 s): Functional2 vs. Imperative
Significantly different (p ~= 0)
Time ratio: 0.89560 95% CI 0.88823 - 0.90297 (n=20)
First 16.95 ms 95% CI 16.85 ms - 17.04 ms
Second 15.18 ms 95% CI 15.08 ms - 15.27 ms
res17: Int = 4500000
Run Code Online (Sandbox Code Playgroud)
由于来自@Odomontois的建议而更新:请注意,如果您真的想要优化它,则必须确保字符串的字符不被加框.这是一个命令式的版本,看起来不是很好,但也几乎尽可能快.这是使用spire中的cfor宏,但while循环也可以正常工作.
def sumi3 = {
var sum = 0
cfor(0)(_ < value.length, _ + 1) { i =>
sum += value(i).asDigit
}
sum
}
scala> th.pbenchOffWarm("Imperative vs. optimized Imperative")(th.Warm(sumi))(th.Warm(sumi3))
Benchmark comparison (in 4.401 s): Imperative vs. optimized Imperative
Significantly different (p ~= 0)
Time ratio: 0.08925 95% CI 0.08880 - 0.08970 (n=20)
First 15.10 ms 95% CI 15.04 ms - 15.16 ms
Second 1.348 ms 95% CI 1.344 ms - 1.351 ms
res9: Int = 4500000
Run Code Online (Sandbox Code Playgroud)
过早优化免责声明:
除非你绝对确定a)一段代码是一个性能瓶颈而且b)命令式版本要快得多,所以我总是更喜欢最可读的版本.Scala 2.12将带有一个新的优化器,它将使功能样式的大量开销小得多,因为它可以在许多情况下进行高级优化,例如闭合内联.