Spark内存管理之钨丝计划
1. 钨丝计划的产生的原因
2. 钨丝计划内幕详解
一:“钨丝计划”产生的本质原因
1, Spark作为一个一体化多元化的(大)数据处理通用平台,性能一直是其根本性的追求之一,Spark基于内存迭代(部分基于磁盘迭代)的模型极大的满足了人们对分布式系统处理性能的渴望,但是有Spark是采用Scala+ Java语言编写的所以运行在了JVM平台,当然JVM是一个绝对伟大的平台,因为JVM让整个离散的主机融为了一体(网络即OS),但是JVM的死穴GC反过来限制了Spark(也就是说平台限制了Spark),所以Tungsten聚焦于CPU和Memory使用,以达到对分布式硬件潜能的终极压榨!
对内存的使用:
On-Heap:对象在JVM堆中。
Off-Heap:在JVM堆外从OS层面分配内存,不受JVM管理。
2, 对Memory的使用,Tungsten使用了Off-Heap(堆外内存),也就是在JVM之外的内存空间(这就好像C语言对内存的分配、使用和销毁),是系统级别的,此时Spark实现了自己的独立的内存管理,就避免了JVM的GC引发的性能问题,其实还包含避免序列化和反序列化。
3, 对于Memory管理方面一个至关重要的内容Cache,Tungsten提出了Cache-aware computation,也就是说使用对缓存友好的算法和数据结构来完成数据的存储和复用;
4, 对于CPU而言,Tungsten提出了Code Generation,其首先在Spark SQL使用,通过Tungsten要把该功能普及到Spark的所有功能中;
总结:Tungsten的内存管理机制独立于JVM,所以Spark操作数据的时候具体操作的是Binary Data,而不是JVM Object!!!而且还免去了序列化和反序列化的过程!!!!!
二:“钨丝计划”内幕详解
1, 内存管理方面:Spark使用了sun.misc.Unsafe来进行Off-heap级别的内存分配、指针使用及内存释放;Spark为了统一管理Off-Heap和On-heap而提出了Page。
2, 如果是On-heap的方式,本身是有一个64bit的Object的引用和一个64bit的在Object中的OffSet来指定具体数据的。如果是Off-Heap那么就是一个指针直接指向数据。
Off-Heap:采用C语言的方式,所以指针直接指向数据实体。
On-heap: 堆内内存空间的时候,GC的时候对堆内结果的重新组织。
3, On-heap:如果在运行的时候分配Java Object对象,它的地址是不可以改变,JVM对内存管理的负担是远远大于实用Off-Heap方式,因为GC的时候会对heap进行相应的重组。
4, Spark进行了一层抽象,访问数据的时候可能在堆内也可能在堆外,提供了管理器,管理器可以根据数据在堆内还是在堆外进行具体的寻址,但是从程序运行的角度或者框架的角度来看,是看不见是堆内还是堆外,管理器会完成具体地址和寻址到具体数据的映射过程。
5, Page会针对堆外内存和堆内内存两种情况进行具体的适配。
6, Page在寻址的时候具体包括两部分,一个是什么样的Page,另一个就是OffSet。
7, 如果在Off-heap下,内存就直接是一个64bit的long的指针来指向具体数据。
8,如果是On-heap下,也就是堆内的情况下,则就会有两部分,一部分是64bit的Object的引用,另外一部分是64bit的Object内的Offset来表示我们具体的数据。
这时候在翻译两种不同情况的时候,就要注意该怎么寻址了。
Off-Heap:如果是一个指针直接指向数据结构。
On-heap: GC会导致heap的重新组织。而重组之后要确保Object的地址是不变的。
Page是一个Table。
Page Table的方式来进行内存管理。把内存分为很多页。页只是一个单位,和分配数组差不多,具体实现是通过TaskMemoryManager对内存进行管理的,具体是靠allocatePage来分配页。
内存寻址:
1. 在哪一页,也就是在那个Page.
2. 就是Page的具体偏移。
内存寻址的工作方式:
Off-heap:那么内存直接就是用一个64bit的long的指针来指向我们的数据。
On-heap:那就是堆内,包含两部分,一部分是64bit的引用,另外一部分是64bit的Object的OffSet来表示我们的具体数据。
地址本身和数据本身之间有一个映射,也就是所谓的逻辑地址,将逻辑地址映射到实际的物理地址,这个是钨丝计划内部的管理机制。
由于逻辑地址是一个64bit的长整数,它前面的13个bit是第几页,后面的51bit就表示在页内的偏移。
所以寻址的方式就是先找到那个page,然后根据后面的51bit,所以加上偏移量,就找到了具体的数据在内存的物理地址。
MemoryLocation:封装了两种逻辑地址寻址的方式。
/**
* A memory location. Tracked either by a memory address (with off-heap allocation),
* or by an offset from a JVM object (in-heap allocation).
*/
public class MemoryLocation {
- allocatePage:分配内存页,添加到Page table中,然后去使用它。
TaskMemoryManager来管理Page,还有管理Off-heap和On-heap的方式
/**
* Allocate a block of memory that will be tracked in the MemoryManager's page table; this is * intended for allocating large blocks of Tungsten memory that will be shared between operators. * * Returns `null` if there was not enough memory to allocate the page. May return a page that * contains fewer bytes than requested, so callers should verify the size of returned pages. */ public MemoryBlock allocatePage(long size, MemoryConsumer consumer) { if (size > MAXIMUM_PAGE_SIZE_BYTES) { throw new IllegalArgumentException( "Cannot allocate a page with more than " + MAXIMUM_PAGE_SIZE_BYTES + " bytes"); }
2. cache aware computation机制:
主要是基于内存的计算,进行的一种优化的方式,CPU Cache的时候l1,l2,l3,速度是不同的,那我们提供的cache的一些友好的数据结构,就可以提高cache的命中率。
3. 提高cache的命中率,怎么做的?
设计了自己的数据结构,以前要命中具体的cache的时候,其实采用遍历的方式,现在加了一个指针不需要遍历,数据要找命中的cache指针的位置已经指向了具体的位置。这个时候查询的方式就按照Key和Point对的方式,这时候就不需要遍历随机访问了。
4. Code Generation机制:
把已有的代码变成本地的字节,不需要很多的抽象和匹配等。
总结:
原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/9276.html