Spark 内存管理之UnifiedMemoryManager

概要

Spark 内存管理之StaticMemoryManager介绍了静态资源管理器,这篇研究另外一个内存管理器UnifiedMemoryManager统一内存管理。UnifiedMemoryManager是Spark 1.6之后默认的内存管理器。

设计

为何引入统一资源管理器

静态资源管理器的storage、execution内存界限是固定的,那么不使用cache的情况下,storage这部分内存会存在利用不充分的问题,调优这部分内容需要开发者熟悉静态内存管理,门槛较高。鉴于此,统一内存管理器打破storage、execution的界限,storage、execution可以互相从对方借用内存。

内存借用策略

storage、execution内存可以互相借用,如下,需要注意的是,如果execution占用了storage的内存,且storage内存也不足时,execution并不会归还这部分占用的内存,反之,storage会归还占用内存(下图来自Apache Spark 内存管理详解)。

为何execution不归还借用的storage内存

Unified Memory Management in Spark 1.6
中详细讲解了为何选择这种策略,简单总结如下

  1. 数据清除的开销
    驱逐storage内存的开销取决于storage level,MEMORY_ONLY可能是最昂贵的,因为需要重新计算,MEMORY_AND_DISK_SER正好相反,只涉及到磁盘IO。溢写execution内存到磁盘的开销并不昂贵,因为execution存储的数据格式紧凑( compact format),序列化开销低。并且,清除的storage内存可能不会被用到,但是,可以预见的是,驱逐的execution内存是必然会再被读到内存的,频繁的驱除重读execution内存将导致昂贵的开销。
  2. 实现的复杂度
    storage内存的驱逐是容易实现的,只需要使用已有的方法,drop掉block。execution则复杂的多,首先,execution以page为单位管理这部分内存,并且确保相应的操作至少有one page,如果把这one page内存驱逐了,对应的操作就会处于饥饿状态。此外,还需要考虑execution内存被驱逐的情况下,等待cache的block如何处理。

UnifiedMemoryManager

和StaticMemoryManager相似,UnifiedMemoryManager继承MemoryManager,并重写了acquireExecutionMemoryacquireStorageMemoryacquireUnrollMemory方法,定义如下

其UML和StaticMemoryManager类似,如下

内存管理

回顾MemoryManager

MemoryManager将内存划分为如下几部分,接下来研究静态内存管理中下面各部分占比
这里写图片描述

统一内存管理

UnifiedMemoryManager会先预留出300M内存,剩下的记作MaxMemory,这部分内存划分为storage、execution、other等,相关代码如下

  1. 300M预留内存
  2. MaxMemory
  3. storage
  4. execution
    这里写图片描述

总结如下

maxmemory total - 300M
execution maxmemory * 0.6 * 0.5
storage maxmemory * 0.6 * 0.5
other maxmemory * 0.4 + 300M

不再细分unroll,统一为storage

MemoryManager在storage内存中细分了unroll,静态内存管理的实现划分了unroll这部分内存,并设置了比例。统一内存管理不再细分unroll,统一为storage。

为什么设置300M预留内存

统一内存管理最初版本other这部分内存没有固定值300M设置,而是和静态内存管理相似,设置的百分比,最初版本占25%。百分比设置在实际使用中出现了问题,若给定的内存较低时,例如1G,会导致OOM,具体讨论参考这里Make unified memory management work with small heaps,因此,other这部分内存做了修改,先划出300M内存。

spark.memory.fraction由0.75 降至 0.6

spark.memory.fraction最初版本的值是0.75,很多分析统一内存管理这块的文章也是这么介绍的,同样的,在使用中发现这个值设置的偏高,导致了gc时间过长,spark 2.0版本将其调整为0.6,详细谈论参见Reduce spark.memory.fraction default to avoid overrunning old gen in JVM default config

从上面两个例子可以看出,调整参数是内存优化的重要手段,并且要和实际的环境相结合,没有一套放之四海而皆准的准则,所以熟悉内存管理的细节显得尤为重要。

总结

介绍了统一内存管理UnifiedMemoryManager出现的缘由,其管理的各内存的占比,以及两个重要参数的调整。

参考:
Unified Memory Management in Spark 1.6

Apache Spark 内存管理详解