【问题标题】:Trying to understand Scala Array试图理解 Scala 数组
【发布时间】:2018-08-18 09:51:01
【问题描述】:

我正在尝试评估哪种数据结构最能代表 Scala 中的稀疏向量。这些稀疏向量包含一个索引列表,以及每个索引的一个值。我实现了一个小基准,这似乎表明Array[(Long, Double)] 似乎比 2 个并行数组占用的空间要少得多。那是对的吗?我是否正确地进行了该基准测试? (如果我在某个地方做错了什么,我不会感到惊讶)

import java.lang.management.ManagementFactory
import java.text.NumberFormat

object TestSize {

  val N = 100000000
  val formatter: NumberFormat = java.text.NumberFormat.getIntegerInstance

  def twoParallelArrays(): Unit = {

    val Z1 = Array.ofDim[Long](N)
    val Z2 = Array.ofDim[Double](N)
    Z1(N-1) = 1
    Z2(N-1) = 1.0D
    println(Z2(N-1) - Z1(N-1))
    val z1 = ManagementFactory.getMemoryMXBean.getHeapMemoryUsage.getUsed
    val z2 = ManagementFactory.getMemoryMXBean.getNonHeapMemoryUsage.getUsed
    println(s"${formatter.format(z1)} ${formatter.format(z2)}")
  }

  def arrayOfTuples(): Unit = {

    val Z = Array.ofDim[(Long, Double)](N)
    Z(N-1) = (1, 1.0D)
    println(Z(N-1)._2 - Z(N-1)._1)
    val z1 = ManagementFactory.getMemoryMXBean.getHeapMemoryUsage.getUsed
    val z2 = ManagementFactory.getMemoryMXBean.getNonHeapMemoryUsage.getUsed
    println(s"${formatter.format(z1)} ${formatter.format(z2)}")
  }

  def main(args: Array[String]): Unit = {

    // Comment one or the other to look at the results
    //arrayOfTuples()
    twoParallelArrays()
  }
}

【问题讨论】:

  • 关于 Scala 需要注意的重要一点:名称的大小写很重要。因此Z1 的名称可能会混淆编译器(和普通读者)。除了少数例外,都遵循 Java 命名约定。
  • @BobDalgleish - 得分。

标签: scala performance performance-testing


【解决方案1】:

不,不正确。

Array.ofDim[(Long, Double)](N)

创建一个用null 填充的大数组,并且不为LongDouble 或实际的Tuple2 实例分配任何空间,这就是为什么在堆内存使用中看不到任何内容的原因.

双数组版本立即为所有LongDouble 分配它需要的所有空间,您可以在堆空间使用情况中看到它。

只需将ofDim 替换为适当的fill 即可查看真实 数字。

在大小为N = 1000000的数组上:

arrayOfTuples:     45,693,312 19,190,296
twoParallelArrays: 25,925,792 19,315,256

arrayOfTuples-解决方案显然需要更多空间。

您可能想知道为什么该因子大约是 1.8 而不是至少 2.5。这是因为Tuple2对于一些原始数据类型是@specialized,特别是对于LongDouble,因此这两个8字节的原始数据可以存储在Tuple2中而无需装箱。因此,从数组到Tuple2 的64 位引用的总开销仅为8 个字节,并且每个Tuple2 实例中都有一些开销。但是,它不仅仅是将LongDouble 直接存储在数组中。

顺便说一句:这正是 Apache Spark 使用所有 Encoders 存储数据的原因:这样您就不必担心元组和案例类造成的开销。


完整代码:

import java.lang.management.ManagementFactory
import java.text.NumberFormat

object TestSize {

  val N = 1000000
  val formatter: NumberFormat = java.text.NumberFormat.getIntegerInstance

  def twoParallelArrays(): Unit = {

    val Z1 = Array.fill[Long](N)(42L)
    val Z2 = Array.fill[Double](N)(42.0)
    println(Z1)
    println(Z2)
    Z1(N-1) = 1
    Z2(N-1) = 1.0D
    println(Z2(N-1) - Z1(N-1))
    val z1 = ManagementFactory.getMemoryMXBean.getHeapMemoryUsage.getUsed
    val z2 = ManagementFactory.getMemoryMXBean.getNonHeapMemoryUsage.getUsed
    Z1((new scala.util.Random).nextInt(N)) = 1234L
    Z2((new scala.util.Random).nextInt(N)) = 345.0d
    println(Z2(N-1) - Z1(N-1))
    println(s"${formatter.format(z1)} ${formatter.format(z2)}")
  }

  def arrayOfTuples(): Unit = {

    val Z = Array.fill[(Long, Double)](N)((42L, 42.0d))
    Z(N-1) = (1, 1.0D)
    println(Z(N-1)._2 - Z(N-1)._1)
    val z1 = ManagementFactory.getMemoryMXBean.getHeapMemoryUsage.getUsed
    val z2 = ManagementFactory.getMemoryMXBean.getNonHeapMemoryUsage.getUsed
    Z((new scala.util.Random).nextInt(N)) = (1234L, 543.0d)
    println(Z(N-1)._2 - Z(N-1)._1)
    println(s"${formatter.format(z1)} ${formatter.format(z2)}")
  }

  def main(args: Array[String]): Unit = {

    // Comment one or the other to look at the results
    arrayOfTuples()
    // twoParallelArrays()
  }
}

【讨论】:

  • 谢谢安德烈!完美 - 我应该初始化/填充这些数组。非常感谢。
  • @Frank 事后看来似乎很明显,但我发现输出实际上非常令人惊讶。很高兴想起Tuple2@specialized 的事实,所以也谢谢你的问题:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-02-03
  • 1970-01-01
  • 2021-12-28
  • 2020-04-28
  • 2022-07-21
  • 1970-01-01
  • 2015-12-15
相关资源
最近更新 更多