Tungsten项目是在Spark 1.4版本引入的,它对Spark执行引擎进行了修改,最大限度地利用现代计算硬件资源,大幅提高了Spark应用程序的内存和CPU效率。
Tungsten项目包括以下改进方案:
Spark实现自己的内存管理的原因主要是Java对象占用较多的内存空间和JVM GC的低效。
我们知道在JVM中,Java对象占用的内存分为3个区域:
从Java对象的内存布局可以知道,一个Java对象首先就要占用掉12字节的长度,还有对其填充占用的空间,这其实非常低效的。
JVM中的垃圾收集将对象分为两类:分配/回收率高的年轻代和回收率低的年老代。要很好的对JVM中的对象进行管理,就需要使用灵活使用各种虚拟机参数进行调优。
此外,大量大数据的工作负载对于JVM GC是非常不友好的,可能会导致长时间的GC停顿,从而导致Spark应用程序效率低下。
基于上面两方面原因,再加上Spark比JVM更加了解所处理数据如何在不同的stage中流动,以及job和task的范围和内存块的生命周期信息,那么Spark就应该能够比JVM更有效地管理内存。
因此,为了解决对象高开销和GC效率低下的问题,Spark基于sun.misc.Unsafe(是JVM内部用来直接操作内存的API,基于此API构建堆/非堆内存数据结构)引入了显式的内存管理,将大多数的操作转换为直接对二进制数据而不是Java对象的操作。
我们知道Spark的高性能数据处理主要得益于其基于内存的计算引擎。而缓存计算就是通过使用CPU的L1/L2/L3集缓存来提高数据的处理速度,这要比基于主内存计算快了一个数量级。
JVM上一些低效操作:
Spark在SQL和DataFrame上引入了表达式求值的代码生成技术。表达式求值是在特定记录上计算表达式值的过程(比如"age > 10 and age < 20"),在运行时,Spark会动态生成字节码来计算这些表达式,而不是低效的解释执行每行记录。代码生成相比一行行的解释执行可以减少对基本数据类型的装箱操作,更重要的是,可以避免多态函数的调用。
此外,代码生成技术不仅可以用到一次一条记录的表达式求值,还以用到向量化表达式的求值,使用JIT的能力来更好的利用现代计算机CPU的流水线指令,这样我们就可以一次处理多条记录。
代码生成还可以用到Spark Shuffle操作上,来提高序列化的吞吐量。