WholeStageCodeGeneration,全阶段代码生成,简称WSCG。
在理解WSCG之前,我们需要弄清楚为啥需要WSCG。
1、为啥需要WSCG?
1.1、火山迭代模型
火山模型(迭代器模型), 是1994年 Goetz Graefe 在他的论文 《Volcano, An Extensible and Parallel Query Evaluation System》中提出的概念。
该模型中的每个操作都由 3 种方法组成:
open() -用于算子的初始化操作,一般也会调用子节点的该方法来初始化整棵树;
next() - 根据算子类型进行具体的实现,首先会调用子节点的Next()方法,获取子节点的数据,进行特定的处理后(该算子的具体实现),向上返回给父节点;
close() - 关闭算子的生命周期,清理状态;
火山迭代模型虽然简单却很强大,非常灵活而具有扩展性,比如单个算子的执行逻辑完全不需要考虑其上下游是什么,也不需要考虑自身是否是并行在执行,这些逻辑都被放到了外部,而自身的策略也是注入式的,可以由外层灵活修改,整个迭代树只负责整体处理流程。
能看出,核心就在于Next()方法的实现,通过不断的调用子节点Next()方法,来实现从下往上的数据传递,就像一个流水线一样执行完整棵树。
其实在spark中用的就这种模式,比如sparksql生成的物理执行计划节点中,会实现next()函数:
1.2、火山迭代模型的缺点
1)虚函数调用
在火山迭代模型中,处理一次数据最少需要调用一次next()函数。
这些函数的调用是由编译器通过虚函数调度实现的。
虚函数慢的原因:
-
虚函数通常通过虚函数表来实现,在虚表中存储函数指针,实际调用时需要间接访问,这需要多一点时间。
-
虚函数其实最主要的性能开销在于它阻碍了编译器内联函数和各种函数级别的优化,导致性能开销较大。比如,在普通函数中log(10)会被优化掉,它就只会被计算一次,而如果使用虚函数,log(10)不会被编译器优化,它就会被计算多次。如果代码中使用了更多的虚函数,编译器能优化的代码就越少,性能就越低。
2)缓存感知(内存与 CPU 寄存器)
在火山迭代模型中,每次算子将数据传递给另一个算子时,都需要将算子放入内存。
在 WSCG 版本中,编译器实际上将中间