文章目录
Spark Tungsten
Tungsten项目是在Spark 1.4版本引入的,它对Spark执行引擎进行了修改,最大限度地利用现代计算硬件资源,大幅提高了Spark应用程序的内存和CPU效率。
Tungsten项目包括以下改进方案:
- Memory Management and Binary Processing:内存管理和二进制处理。Spark显示地管理内存,消除了JVM对象模型和垃圾收集的开销。
- Cache-aware computation:支持缓存计算。利用CPU缓存提升数据处理效率。
- Code generation:代码生成。使用代码生成技术高效利用现代编译器和CPU。
- No virtual function dispatches:无虚函数调用。减少CPU调用次数。如果存在数十亿次的虚函数的调用,将大大影响CPU性能。
- Intermediate data in memory vs CPU registers:中间数据在内存 vs CPU寄存器。Tungsten项目第二阶段,将临时数据存储在CPU寄存器中。从CPU寄存器获取数据的效率要比从主内存高出一个数量级。
- Loop unrolling and SIMD:循环展开和SIMD。优化Spark执行引擎,以利用现代编译器和CPU高效地编译和执行简单的for循环。
Memory Management and Binary Processing
Spark实现自己的内存管理的原因主要是Java对象占用较多的内存空间和JVM GC的低效。
1. Java对象内存占用高
我们知道在JVM中,Java对象占用的内存分为3个区域:
- 对象头,占用12个字节。
- 实例数据,根据本身数据长度。
- 对其填充,整个对象大小必须为8字节的倍数,如果不够8的倍数,就填充为8的倍数。
从Java对象的内存布局可以知道,一个Java对象首先就要占用掉12字节的长度,还有对其填充占用的空间,这其实非常低效的。
2. JVM GC效率低
JVM中的垃圾收集将对象分为两类:分配/回收率高的年轻代和回收率低的年老代。要很好的对JVM中的对象进行管理,就需要使用灵活使用各种虚拟机参数进行调优。
此外,大量大数据的工作负载对于JVM GC是非常不友好的,可能会导致长时间的GC停顿,从而导致Spark应用程序效率低下。
基于上面两方面原因,再加上Spark比JVM更加了解所处理数据如何在不同的stage中流动,以及job和task的范围和内存块的生命周期信息,那么Spark就应该能够比JVM更有效地管理内存。
因此,为了解决对象高开销和GC效率低下的问题,Spark基于sun.misc.Unsafe(是JVM内部用来直接操作内存的API,基于此API构建堆/非堆内存数据结构)引入了显式的内存管理,将大多数的操作转换为直接对二进制数据而不是Java对象的操作。
Cache-aware Computation
我们知道Spark的高性能数据处理主要得益于其基于内存的计算引擎。而缓存计算就是通过使用CPU的L1/L2/L3集缓存来提高数据的处理速度,这要比基于主内存计算快了一个数量级。
Code Generation
JVM上一些低效操作:
- 虚函数调用
- 创建对象时基本数据类型的装箱操作
- 基本数据类型对应的引用类型占用了更多的内存
Spark在SQL和DataFrame上引入了表达式求值的代码生成技术。表达式求值是在特定记录上计算表达式值的过程(比如"age > 10 and age < 20"),在运行时,Spark会动态生成字节码来计算这些表达式,而不是低效的解释执行每行记录。代码生成相比一行行的解释执行可以减少对基本数据类型的装箱操作,更重要的是,可以避免多态函数的调用。
此外,代码生成技术不仅可以用到一次一条记录的表达式求值,还以用到向量化表达式的求值,使用JIT的能力来更好的利用现代计算机CPU的流水线指令,这样我们就可以一次处理多条记录。
代码生成还可以用到Spark Shuffle操作上,来提高序列化的吞吐量。