1、背景
Spark Codegen是在CBO&RBO后,将算子的底层逻辑用代码来实现的一种优化。
具体包括Expression级别和WholeStage级别的Codegen。
2、举例说明
① Expression级别:摘一个网上的例子:x + (1 + 2)
用scala代码表示:
Add(Attribute(x), Add(Literal(1), Literal(2)))
语法树如下:
递归求值这棵语法树的常规代码如下:
tree.transformUp {
case Attribute(idx) => Literal(row.getValue(idx))
case Add(Literal(c1),Literal(c2)) => Literal(c1+c2)
case Literal(c) => Literal(c)
}
执行上述代码需要做很多类型匹配、虚函数调用、对象创建等额外逻辑,这些overhead远超对表达式求值本身。
为了消除这些overhead,Spark Codegen直接拼成求值表达式的java代码并进行即时编译。具体分为三个步骤:
- 代码生成。根据语法树生成java代码,封装在wrapper类中:
... // class wrapper
row.getValue(idx) + (1 + 2)
... // class wrapper
- 即时编译。使用Janino框架把生成代码编译成class文件。
- 加载执行。最后加载并执行。
优化前后性能有数量级的提升。
② WholeStage级别 举一个稍微复杂的例子,并详细分析一下
看了上面的例子,应该先有了一个大致的印象,接下来看下:
SQL:
select * from test.zyz where id=1;
表结构:
CREATE TABLE `test.zyz`(
`id` int,
`name` string)
PARTITIONED BY (
`pt` int)
ROW FORMAT SERDE
'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe'
STORED AS INPUTFORMAT
'org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat'
OUTPUTFORMAT
'org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat'
执行计划:
Found 1 WholeStageCodegen subtrees.
== Subtree 1 / 1 ==
*(1) Project [id#11, name#12, pt#13]
+- *(1) Filter (isnotnull(id#11) && (id#11 = 1))
+- *(1) FileScan parquet test.zyz[id#11,name#12,pt#13] Batched: true, Format: Parquet, Location: CatalogFileIndex[hdfs://nameservice1/user/hive/wareh