在Scala中对集合求和的最快方法是什么

在Scala中对集合求和的最快方法是什么,scala,scala-collections,Scala,Scala Collections,我在Scala中尝试了不同的集合来求和它的元素,它们比Java求和它的数组慢得多(使用forcycle)。有没有办法让Scala和Java数组一样快 我听说scala 2.8中的数组与java中的数组相同,但实际上速度要慢得多scala 2.8Array是JVM/java数组,因此具有相同的性能特征。但这意味着它们不能直接使用额外的方法将它们与Scala集合的其余部分统一起来。为了提供数组具有这些方法的假象,对添加这些功能的包装类进行了隐式转换。如果不小心的话,使用这些功能会产生过多的开销 在迭

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


我听说scala 2.8中的数组与java中的数组相同,但实际上速度要慢得多

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

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

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

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

在while循环中索引到数组在Scala中的速度与Java中的速度一样快。(Scala的“for”循环不是Java的低级构造,因此它不会按您想要的方式工作。)

因此,如果您在Java中看到

for (int i=0 ; i < array.length ; i++) sum += array(i)
然后,在Scala中,如果使用类似ArrayBuffer的等效数据结构,您将与

两个都离目标不远

sum = (0 /: arraybuffer)(_ + _)
sum = arraybuffer.sum  // 2.8 only
但是,请记住,混合使用高级别和低级别构造是有代价的。例如,如果您决定从一个数组开始,然后对其使用“foreach”而不是索引,Scala必须将其包装在一个集合中(2.8中的
ArrayOps
)才能使其工作,并且通常还必须将原语装箱

无论如何,对于基准测试,这两个函数是您的朋友:

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) }
def时间[F](F:=>F)={
val t0=System.nanoTime
val ans=f
printf(“已用时间:%.3f\n”,1e-9*(System.nanoTime-t0))
ans
}
定义批次[F](n:Int,F:=>F):F=if(n批次(3,时间)(批次(100,(0.0/:ab)())
时间:1.694
时间:1.679
时间:1.635
res5:Double=4.99999e11
//高级收集,操作更简单
scala>lots(3个,时间(lots(100,{var s=0.0;ab.foreach(s+=;;s})))
时间:1.171
时间:1.166
时间:1.162
res7:Double=4.99999e11
//所有低级操作都使用原语,无需装箱,快速!
scala>批次(3,时间(批次(100,adSum(a)))
时间:0.185
时间:0.183
时间:0.186
res6:Double=4.99999e11

很难解释为什么您没有展示的某些代码的性能比您没有展示的某些基准测试中没有展示的其他代码差


首先,您可能会对它及其公认的答案感兴趣。但是,对JVM代码进行基准测试是困难的,因为JIT将以难以预测的方式优化代码(这就是为什么JIT在编译时优于传统优化)。

正确的scala或functional方法是:

val numbers = Array(1, 2, 3, 4, 5)
val sum = numbers.reduceLeft[Int](_+_)
有关语法的完整解释,请查看此链接:


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

您现在可以简单地使用sum

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

val sumOfValues = values.sum

时机不是唯一的问题。 使用
sum
可能会发现溢出问题:

scala> Array(2147483647,2147483647).sum
res0: Int = -2
在这种情况下,最好使用
Long
进行
foldLeft
播种

scala> Array(2147483647,2147483647).foldLeft(0L)(_+_)
res1: Long = 4294967294
编辑:
Long
可以从一开始就使用:

scala> Array(2147483647L,2147483647L).sum
res1: Long = 4294967294

你好,Randall。谢谢你的回答。我用你的答案在Java和Scala中做了一个测试,增加了1000万个倍增,结果是23.23ms对141ms。那么,还有什么可以帮助的吗?@Tala:通常的基准测试警告适用。你知道微基准测试JVM代码的问题吗?Scala 2.8,类似于IterableLike:“def foreach[U](f:a=>U):Unit=iterator.foreach(f)“iterator:“def foreach[U](f:A=>U){while(hasNext)f(next())}”假设f不需要装箱(因为“@specialized”),l1.foreach应该具有与Randall的while循环几乎相同的性能,不是吗?@Sandor Murakozi:也许吧。while循环不使用
函数
对象,而
foreach
对象。根据循环中代码的细节和JIT编译器的性能,代码可以内联。当然,如果块体很大,那么子例程调用开销可能很小。唯一可以肯定的是,如果它真的是性能关键代码,那么应该制定一个适当的基准测试。向我们展示您的基准测试代码。@Daniel-
a.sum
大约需要
(0/:a)(+\uu)
,至少从2.8.0.RC6开始。@Don-请记住,“lots”本身并没有while循环快。因此,不要把非常非常便宜的东西放在里面,并期望得到准确的时间。
val values = Array.fill[Double](numValues)(0)

val sumOfValues = values.sum
scala> Array(2147483647,2147483647).sum
res0: Int = -2
scala> Array(2147483647,2147483647).foldLeft(0L)(_+_)
res1: Long = 4294967294
scala> Array(2147483647L,2147483647L).sum
res1: Long = 4294967294