scala - 为什么在 Scala 中压缩比 zip 快?

标签 scala performance scala-collections jmh elementwise-operations

我编写了一些 Scala 代码来对集合执行元素操作。在这里,我定义了两个执行相同任务的方法。一种方法使用 zip其他用途zipped .

def ES (arr :Array[Double], arr1 :Array[Double]) :Array[Double] = arr.zip(arr1).map(x => x._1 + x._2)

def ES1(arr :Array[Double], arr1 :Array[Double]) :Array[Double] = (arr,arr1).zipped.map((x,y) => x + y)

为了在速度方面比较这两种方法,我编写了以下代码:
def fun (arr : Array[Double] , arr1 : Array[Double] , f :(Array[Double],Array[Double]) => Array[Double] , itr : Int) ={
  val t0 = System.nanoTime()
  for (i <- 1 to itr) {
       f(arr,arr1)
       }
  val t1 = System.nanoTime()
  println("Total Time Consumed:" + ((t1 - t0).toDouble / 1000000000).toDouble + "Seconds")
}

我调用 fun方法和通行证ESES1如下:
fun(Array.fill(10000)(math.random), Array.fill(10000)(math.random), ES , 100000)
fun(Array.fill(10000)(math.random), Array.fill(10000)(math.random), ES1, 100000)

结果表明,该方法名为ES1使用 zipped比方法更快 ES使用 zip .
基于这些观察,我有两个问题。

为什么是 zippedzip 快?

有没有更快的方法对 Scala 中的集合进行元素操作?

最佳答案

回答你的第二个问题:

Is there any more faster way to do element wise operation on a collection in Scala?


可悲的事实是,尽管它简洁、提高了生产力和对错误的适应能力,但函数式语言不一定是最高效的。在 OP 的示例中,使用高阶函数来定义要针对集合执行的投影具有开销,并且紧密循环会放大这一点。正如其他人指出的那样,中间和最终结果的额外存储分配也会产生开销。
如果性能很重要,尽管绝不是通用的,在像您这样的情况下,您可以将简洁的功能代码展开回命令式等效代码,以便重新获得对内存使用的更直接控制并消除函数调用开销。
在您的具体示例中, zipped可以通过预先分配正确大小的固定可变数组来强制执行求和(因为当其中一个集合用完元素时 zip 停止),然后将适当索引处的元素添加在一起(因为按序数索引访问数组元素是一个非常快的操作)。
例如,添加第三个函数,ES3到您的测试套件:
def ES3(arr :Array[Double], arr1 :Array[Double]) :Array[Double] = {
   val minSize = math.min(arr.length, arr1.length)
   val array = Array.ofDim[Double](minSize)
   for (i <- 0 to minSize - 1) {
     array(i) = arr(i) + arr1(i)
   }
  array
}
在我的 i7 上,我得到以下响应时间:
OP ES Total Time Consumed:23.3747857Seconds
OP ES1 Total Time Consumed:11.7506995Seconds
--
ES3 Total Time Consumed:1.0255231Seconds
更令人发指的是对两个数组中较短的数组进行直接就地变异,这显然会破坏这个较短数组的内容,因此只有在不需要原始数组进行进一步工作时才应该实现调用者:
def ES4(arr :Array[Double], arr1 :Array[Double]) :Array[Double] = {
   val minSize = math.min(arr.length, arr1.length)
   val array = if (arr.length < arr1.length) arr else arr1
   for (i <- 0 to minSize - 1) {
      array(i) = arr(i) + arr1(i)
   }
  array
}

Total Time Consumed:0.3542098Seconds
但显然,直接改变作为参数传递的数组元素并不符合 Scala 的精神——这段代码有副作用。
老实说,如果您需要在紧密循环中进行这种级别的性能优化,那么最好用 Rust、Go 或 C 编写这些类型的算法。

关于scala - 为什么在 Scala 中压缩比 zip 快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59598239/

相关文章:

scala - 使用内部 DSL 重写模式匹配?

java - 无法在 Scala 中迭代 Java 列表

scala - Scala Stream 中的#::运算符是什么?

oracle - 如何判断一张表最近一个月是否被访问过?

mysql - 大表的性能问题

sql - MySQL:使用 IN 和 ORDER BY 时避免文件排序

java - 如何在scala中处理列表列表并迭代添加元素

scala - 列表缓冲区前置 : error: value++=: is not a member of Seq[Int]

scala - 在 Scala : Map(<stuff>: _*) or <stuff>. toMap 中哪一个更惯用/更喜欢?

java - 在 Apache Spark 中,我可以轻松地重复/嵌套 SparkContext.parallelize 吗?