哪些Scala特性性能较差?

5
最近我在思考:由于Scala是运行在JVM上的,而JVM优化了某些类型的操作,因此是否有一些特性的实现在JVM上确实非常低效,使用这些特性应该被阻止?你能否解释一下它们为什么不高效?
第一个可能的候选者就是函数式编程的特性 - 据我所知,函数是具有“apply”方法的特殊类,这显然会产生额外的开销,相比于那些函数只是代码块的语言来说。

这个问题需要一本书才能充分回答。 - Rex Kerr
1个回答

8

性能调优是一个深奥而复杂的问题,但有三件事情需要立即注意。

Scala集合对于表达能力很好,但不适用于性能。

考虑以下内容:

(1 to 20).map(x => x*x).sum

val a = new Array[Int](20)
var i = 0
while (i < 20) { a(i) = i+1; i += 1 }  // (1 to 20)
i = 0
while (i < 20) { a(i) = a(i)*a(i); i += 1 }   // map(x => x*x)
var s = 0
i = 0
while (i < 20) { s += a(i); i += 1 }  // sum
s

第一个函数非常紧凑,第二个函数快16倍。整数的数学计算非常快,装箱和拆箱则不是。通用集合代码是泛型的,并依赖于装箱。
Function2仅对Int,Long和Double参数进行了特化。
对基元类型进行其他操作需要进行装箱,请注意!
假设您想要编写一个函数,可以切换功能 - 也许您想要大写或不大写字母。您尝试:
def doOdd(a: Array[Char], f: (Char, Boolean) => Char) = {
  var i = 0
  while (i<a.length) { a(i) = f(a(i), (i&1)==1); i += 1 }
  a
}

然后你

val text = "The quick brown fox jumps over the lazy dog".toArray
val f = (c: Char, b: Boolean) => if (b) c.toUpper else c.toLower

scala> println( doOdd(text, f).mkString )
tHe qUiCk bRoWn fOx jUmPs oVeR ThE LaZy dOg

好的,没问题!除了我们要怎么做呢?如果我们
trait Func_CB_C { def apply(c: Char, b: Boolean): Char }
val g = new Func_CB_C {
  def apply(c: Char, b: Boolean) = if (b) c.toUpper else c.toLower
}
def doOdd2(a: Array[Char], f: Func_CB_C) = {
  var i = 0
  while (i<a.length) { a(i) = f(a(i), (i&1)==1); i += 1 }
  a
}

需要进行翻译的内容如下:

如果你有一个函数,它带有两个参数并返回一个整数,那么你可能想自己编写一个双重循环,而不是使用 mapflatten。这样做可以让你以3倍的速度运行。但如果它是(Int, Int) => Int(或任何其他Int / Long / Double参数和Unit / Boolean / Int / Long / Float / Double返回值的排列方式),那么编写自己的代码就是没有必要的 - 它是专门的且在最大速度下工作。

仅仅因为易于并行化并不表示这是个好主意。

Scala的并行集合只会尝试并行运行您的代码。你需要确保有足够的工作量使得并行运行变得明智。设置线程和收集结果会带来很多开销。例如,考虑以下情况:

val v = (1 to 1000).to[Vector]
v.map(x => x*(x+1))

对比

val u = (1 to 1000).to[Vector].par
u.map(x => x*(x+1))

第二个地图更快,因为它是并行的吗?
几乎不是!由于开销问题,它比第一个慢10倍(在我的机器上;结果可能会有很大差异)。
总结
这些只是你通常不必担心的问题之一,除非是代码中最需要性能的部分。还有很多问题,最终你会遇到它们,但正如我在评论中提到的那样,涵盖其中相当一部分需要一本书。请注意,任何语言都存在许多性能问题,而优化通常很棘手。请把你的精力留给真正重要的地方!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接