Scala中几种数据结构内存使用的对比

Posted by AlstonWilliams on June 8, 2019

最近跑Spark任务的时候,在Spark UI中,看到GC时间特别长。定位到对应的代码以后,发现代码中使用了Scala的某些数据结构,会产生新的对象。

尽管最终发现是前人留下的代码有问题,但是在排查的过程中查看了一些Scala的数据结构,对比它们的内存用量,真是不比不知道,一比吓一跳。

代码如下:

package com.hypers.insight

import java.util.Collections

import org.apache.spark.util.SizeEstimator

object TestScalaIterator {

    def main(args: Array[String]): Unit = {
        val intTuple = (1L, 2L)
        val strTuple = ("1", "2")
        val intTupleSeq = Seq(intTuple)
        val intTupleSeqIterator = intTupleSeq.iterator
        val intArray = Array(1L, 2L)
        val intTupleArray = Array(intTuple)
        val intTupleArrayIterator = intTupleArray.iterator
        val intTupleSingletonSet = Collections.singleton(intTuple).iterator()
        val intTupleSingletonList = Collections.singletonList(intTuple).iterator()

        println("-------- intTuple size: " + SizeEstimator.estimate(intTuple))
        println("-------- strTuple size: " + SizeEstimator.estimate(strTuple))
        println("-------- intTupleSeq size: " + SizeEstimator.estimate(intTupleSeq))
        println("-------- intTupleSeqIterator size: " + SizeEstimator.estimate(intTupleSeqIterator))
        println("-------- intArray size: " + SizeEstimator.estimate(intArray))
        println("-------- intTupleArray size: " + SizeEstimator.estimate(intTupleArray))
        println("-------- intTupleArrayIterator size: " + SizeEstimator.estimate(intTupleArrayIterator))
        println("-------- intTupleSingletonSet size: " + SizeEstimator.estimate(intTupleSingletonSet))
        println("-------- intTupleSingletonList size: " + SizeEstimator.estimate(intTupleSingletonList))
    }

}

结果如下:

-------- intTuple size: 40
-------- strTuple size: 120
-------- intTupleSeq size: 80
-------- intTupleSeqIterator size: 104
-------- intArray size: 32
-------- intTupleArray size: 64
-------- intTupleArrayIterator size: 104
-------- intTupleSingletonSet size: 64
-------- intTupleSingletonList size: 64

我们可以看到,有这么几点需要注意:

  • 同样都是一个Tuple2,但是即使使用Long,占的内存也只有String的1/3。所以我们代码中,能用数值型尽量用数值型,别图方便用String类型
  • 即使Seq中只有一个元素,它的大小依然是原始数据的两倍大。转换成iterator以后,更是比原始数据大了2.5倍。而Array只有一个元素时,只比原始数据大1.5倍。所以Array其实比Seq省内存
  • Array比Tuple2省内存。上面只是拿Long做对比的,我相信如果用String做对比,或者不是Tuple2,而是Tuple2等,这个差距会更大。
  • Seq和Array得到的iterator,占用的内存是相等的。
  • 如果仅有一个元素,Java的Collections得到的SingletonList或者SingletonSet使用的内存,相对于Scala,少了一半。

Spark写HBase的时候,调用bulkload时,有个非常蛋疼的问题,就是一定要传入一个(T) => Iterator[(KeyFamilyQualifier, Array[Byte])]数据结构,而我们的场景中其实Iterator中仅有一个元素,所以我们必须通过iterator的方式扩大内存的用量。而在所有的产生iterator的方法中,Java的SingletonList/SingletonSet占用的内存更小。