25

我在 Scala 中尝试了不同的集合来求和它的元素,它们比 Java 和它的数组(带for循环)要慢得多。有没有办法让 Scala 和 Java 数组一样快?

我听说 scala 2.8 中的数组与 java 中的相同,但在实践中它们要慢得多

4

6 回答 6

30

Indexing into arrays in a while loop is as fast in Scala as in Java. (Scala's "for" loop is not the low-level construct that Java's is, so that won't work the way you want.)

Thus if in Java you see

for (int i=0 ; i < array.length ; i++) sum += array(i)

in Scala you should write

var i=0
while (i < array.length) {
  sum += array(i)
  i += 1
}

and if you do your benchmarks appropriately, you'll find no difference in speed.

If you have iterators anyway, then Scala is as fast as Java in most things. For example, if you have an ArrayList of doubles and in Java you add them using

for (double d : arraylist) { sum += d }

then in Scala you'll be approximately as fast--if using an equivalent data structure like ArrayBuffer--with

arraybuffer.foreach( sum += _ )

and not too far off the mark with either of

sum = (0 /: arraybuffer)(_ + _)
sum = arraybuffer.sum  // 2.8 only

Keep in mind, though, that there's a penalty to mixing high-level and low-level constructs. For example, if you decide to start with an array but then use "foreach" on it instead of indexing into it, Scala has to wrap it in a collection (ArrayOps in 2.8) to get it to work, and often will have to box the primitives as well.

Anyway, for benchmark testing, these two functions are your friends:

def time[F](f: => F) = {
  val t0 = System.nanoTime
  val ans = f
  printf("Elapsed: %.3f\n",1e-9*(System.nanoTime-t0))
  ans
}

def lots[F](n: Int, f: => F): F = if (n <= 1) f else { f; lots(n-1,f) }

For example:

val a = Array.tabulate(1000000)(_.toDouble)
val ab = new collection.mutable.ArrayBuffer[Double] ++ a
def adSum(ad: Array[Double]) = {
  var sum = 0.0
  var i = 0
  while (i<ad.length) { sum += ad(i); i += 1 }
  sum
}

// Mixed array + high-level; convenient, not so fast
scala> lots(3, time( lots(100,(0.0 /: a)(_ + _)) ) )
Elapsed: 2.434
Elapsed: 2.085
Elapsed: 2.081
res4: Double = 4.999995E11

// High-level container and operations, somewhat better
scala> lots(3, time( lots(100,(0.0 /: ab)(_ + _)) ) )    
Elapsed: 1.694
Elapsed: 1.679
Elapsed: 1.635
res5: Double = 4.999995E11

// High-level collection with simpler operation
scala> lots(3, time( lots(100,{var s=0.0;ab.foreach(s += _);s}) ) )
Elapsed: 1.171
Elapsed: 1.166
Elapsed: 1.162
res7: Double = 4.999995E11

// All low level operations with primitives, no boxing, fast!
scala> lots(3, time( lots(100,adSum(a)) ) )              
Elapsed: 0.185
Elapsed: 0.183
Elapsed: 0.186
res6: Double = 4.999995E11
于 2010-06-23T17:01:00.997 回答
17

您现在可以简单地使用 sum。

val values = Array.fill[Double](numValues)(0)

val sumOfValues = values.sum
于 2015-02-04T02:09:19.800 回答
7

正确的 scala 或功能是这样做的:

val numbers = Array(1, 2, 3, 4, 5)
val sum = numbers.reduceLeft[Int](_+_)

查看此链接以获取语法的完整说明:http: //www.codecommit.com/blog/scala/quick-explanation-of-scalas-syntax

我怀疑这会比其他答案中描述的方式更快,但我没有测试过,所以我不确定。在我看来,这是正确的做法,因为 Scala 是一种函数式语言。

于 2015-01-29T10:45:57.397 回答
6

很难解释为什么您未展示的某些代码比您未展示的某些基准测试中未展示的其他代码执行得更差。

一方面,您可能对这个问题及其接受的答案感兴趣。但是对 JVM 代码进行基准测试很难,因为 JIT 将以难以预测的方式优化代码(这就是 JIT 在编译时优于传统优化的原因)。

于 2010-06-23T17:09:09.530 回答
4

Scala 2.8Array JVM / Java 数组,因此具有相同的性能特征。但这意味着它们不能直接拥有额外的方法来将它们与其他 Scala 集合统一起来。为了提供数组具有这些方法的错觉,对添加这些功能的包装类进行了隐式转换。如果您不小心,使用这些功能会产生过多的开销。

在迭代开销很关键的情况下,您可以显式获取一个迭代器(或维护一个整数索引,用于索引顺序结构,如Arrayor other IndexedSeq)并使用while循环,这是一种不需要对函数进行操作的语言级构造(文字或其他)但可以编译内联代码块。

val l1 = List(...) // or any Iteralbe
val i1 = l1.iterator
while (i1.hasNext) {
  val e = i1.next
  // Do stuff with e
}

此类代码的执行速度基本上与 Java 对应代码一样快。

于 2010-06-23T15:36:16.900 回答
3

时间不是唯一的问题。您sum可能会发现溢出问题:

scala> Array(2147483647,2147483647).sum
res0: Int = -2

在这种情况下,最好foldLeft用 a播种Long

scala> Array(2147483647,2147483647).foldLeft(0L)(_+_)
res1: Long = 4294967294

编辑: Long可以从头开始使用:

scala> Array(2147483647L,2147483647L).sum
res1: Long = 4294967294
于 2016-10-21T12:10:00.897 回答