试图理解 Scala 数组

Trying to understand Scala Array

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

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

不,不正确。

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

创建一个由 null 填充的大数组,并且不为 LongDouble 或实际的 Tuple2 实例分配任何 space ,这就是为什么您在 heap-memory 用法中看不到任何内容。

two-array 版本立即为所有 LongDouble 分配它需要的所有 space,并且您会在堆 space 用法中看到它。

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

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

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

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

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

顺便说一句:这正是 Apache Spark 使用所有这些 Encoder 存储数据的原因:这样您就不必担心元组和 case-classes.


完整代码:

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