Spark SizeTrackingAppendOnlyMap 相关源代码分析

SizeTrackingAppendOnlyMap 在 ExternalAppendOnlyMap 中使用,当 map 中对象占用的内存大小超过一定阈值时,把 数据 spill 到磁盘。所以 SizeTrackingAppendOnlyMap extends AppendOnlyMap[K, V] with SizeTracker

AppendOnlyMap

AppendOnlyMap 是只能增加和修改元素的 Map,不能删除元素。key 可以为 null, key 为 null 的 value 专门用一个字段 nullValue 存储. 用一个字段 haveNullValue 标识是否包含 key 为 null 的 value. 其他数据用一个数组 data 存储。

AppendOnlyMap capacity 默认是 64。mask 为 catacity - 1. data 容量为 (2 * capacity),因为同时存储 key 和 value。 LOAD_FACTOR=0.7。当存储了 capacity * 0.7个元素时,构建新的 data 数组,capacity 翻倍。然后读取原来 data 中的内容,安照新的容量重新计算对应位置,然后放到新的数组里。

插入数据时,先计算 var pos = rehash(k.hashCode) & mask,key 存储在 data[2pos], value 存储在 data[2pos + 1]。如果已经有数据存在,则 pos + 1.

AppendOnlyMap 最多存储 375809638 个元素。

  • destructiveSortedIterator() 方法,提供按 key 排序的 Iterator,可以按原址排序,不需要额外的内存空间, 但是破坏了数据在 data 中的索引, 不能再插入数据。

SizeTracker

SizeTracker 跟踪估计的内存使用量。使用以下字段。

numUpdates: Map 总共插入和修改的次数。
nextSampleNum: 当 numUpdates 等于此时,计算当前对象占用内存和每个更新占用的内存。
SAMPLE_GROWTH_RATE: 1.1, 计算内存计算量后, nextSampleNum = nextSampleNum * SAMPLE_GROWTH_RATE
samples: 存储最后两次评估的数据。Sample 有两个字段 size: Long(占用内存), numUpdates: Long(更新次数)
bytesPerUpdate: 最后两次 samples 的每次 update 平均字节数。(latest.size - previous.size).toDouble / (latest.numUpdates - previous.numUpdates)

当 numUpdates == nextSampleNum 进行精准的内存计算。否则使用以下的方法进行估计。

def estimateSize(): Long = {
    assert(samples.nonEmpty)
    val extrapolatedDelta = bytesPerUpdate * (numUpdates - samples.last.numUpdates)
    (samples.last.size + extrapolatedDelta).toLong
  }

SizeEstimator.estimate 估计对象占用内存

虽然也是用 estimate 代表估计,但是是根据实际每个对象,每个引用都计算的,比较精确。
评估一个 class 实例占用的内存,如果字段是基本类型,占用的内存大小如下表。

private val BYTE_SIZE = 1
private val BOOLEAN_SIZE = 1
private val CHAR_SIZE = 2
private val SHORT_SIZE = 2
private val INT_SIZE = 4
private val LONG_SIZE = 8
private val FLOAT_SIZE = 4
private val DOUBLE_SIZE = 8

如果字段是 object, 则最小是 8,根据是否64位系统和是否开启指针压缩,可能为 12 或者 16.

  • estimate 过程
    使用广度优先算法,需要遍历的 class 对象放到队列里。依次从队列里取 class 对象,
    先获取本 class 的 classInfo。拿到本对象的 fileds 的大小,然后把所有对象类型的字段,都放到队列里。
    基本数据类型的字段不需要。
val classInfo = getClassInfo(cls)
state.size += alignSize(classInfo.shellSize)
for (field <- classInfo.pointerFields) {
 state.enqueue(field.get(obj))
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值