最近跑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占用的内存更小。