繁体   English   中英

在Scala中汇总集合的最快方法是什么?

[英]What is the fastest way to sum a collection in Scala

我已经在Scala中尝试了不同的集合来对它的元素进行求和,它们比Java总和它的数组要快得多( for循环)。 Scala有没有像Java数组一样快的方法?

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

在Scala中对数组进行索引在Scala中与在Java中一样快。 (Scala的“for”循环不是Java的低级构造,因此不会按照你想要的方式工作。)

因此,如果在Java中你看到

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

在Scala你应该写

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

如果你适当地做基准测试,你会发现速度没有区别。

如果你还有迭代器,那么Scala在大多数情况下都和Java一样快。 例如,如果您有一个双打的ArrayList,并且在Java中使用它们添加它们

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

然后在Scala中,如果使用像ArrayBuffer这样的等效数据结构,你的速度大约相同

arraybuffer.foreach( sum += _ )

与任何一个并没有太远的距离

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) }

例如:

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

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

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

val sumOfValues = values.sum

很难解释为什么你没有显示的某些代码比你未在某些基准测试中未显示的其他代码表现更差。

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

适当的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是一种功能语言。

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对应代码一样快地执行。

时机不是唯一的问题。 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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM