SparkSQL代码生成 - 基本表达式代码生成源码解析

参考 : JIT 代码生成技术(一)表达式编译

代码生成

Spark 引入代码生成主要用于 SQL 和 DataFrames 中的表达式求值(Expression evaluation) 。

JavaCode和Block

JavaCode就是含有code字符串的trait。
在这里插入图片描述
Block代表一段Java code。 _marginChar表示应该从每行中删除的前导前缀。可以根据每行的前缀解析出Java代码。
在这里插入图片描述

ExprValue

在这里插入图片描述

ExprValue是有类型的java表达式片段(即一个Java 变量或者常量或者计算式)。其有四个子类:
在这里插入图片描述

  • VariableValue表示一个局部变量表达式。

  • GlobalValue表示一个全局变量表达式。

  • LiteralValue表示一个字面量表达式(有两个子类:TrueLiteral和FalseLiteral)。

  • SimpleExprValue表示一个有类型java表达式片段(例如:“1+1”)。
    在这里插入图片描述
    ExprValue本质内容还是JavaCode中的字符串,只是增加了Java类型。

ExprCode

在这里插入图片描述
ExprCode表示一段Java代码,使用InternalRow作为输入,计算一个Expression。

code表示计算表达式的Java代码块(如果isNull和value已经存在,code中含有空字符串)。isNull表示表达式计算结果是否是Null,value表示表达式计算代码块返回的结果(如果isNull为true,那么value是无效的)。

ExprCode本质内容是JavaCode中的字符串,只是它增加了isNull和Value(ExprValue类型,可能是变量、常量或者表达式),用于表示是否为空和返回值信息。

CodegenContext

CodegenContext:作为代码生成的上下文,记录了将要生成的代码中的各种元素,包括变量、函数等。
在这里插入图片描述

mutableStates

mutableStates表示生成的代码中的所有变量。类型为三元字符串(javaType,
variableName, initCode)构成的数组, 其中的字符串分别代表 Java 类型、
变量名称和变量初始化代码。三元组(“int”,“count”,“count= 0;”)将在生成的类中作为成员变量 count 即“private int count;“,同时在类的初始化函数中加入变量初始化的代码即“count= 0”。

变量数组 mutableStates 相关的方法有 4 个:

  1. addMutableState 方法用来添加变量,需要指定java 类型、变量名称和变量初始化代码。

  2. addBufferedState 方法用来添加缓冲变量,与常规的状态变量的不同之处是,缓冲变量一般用来存储来自 InternalRow 中的数据,比如一行数据 中的某些列等。 因此,这些变量仅在类中声明,但是不会在初始化函数中执行,该方法返回的是 ExprCode 对象。

  3. declareMutableStates 方法用来在生成的java 类中声明这些变量 (默认均为 private 类型)。

  4. initMutableStates 方法用来在类的初始化函数中生成变量的初始化代码,输出的元素都是每行一个。

partitionlnitializationStatements

除常见的变量外,对于 Spark RDD 的处理,有些处理逻辑中可能会涉及 RDD 分区的下标 (partitionlndex) 。 针对这些内容, CodegenContext 中也会进行保存。partitionlnitializationStatements,作为字符串类型的数组

相关方法:

  1. 添加相关代码的方法:addPartitionlnitializationStatement

  2. 代码初始化相关的 initPartition 方法。

references

同样的, references 也是一个数组,用来保存生成代码中的对象(objects),可以通过 addReferenceObj 方法添加。

addedFunctions

除变量外,生成的代码中另一个比较重要的部分是添加的函数。**addedFunctions
类型为 Map[String, String],**提供了函数名和函数代码的映射关系。 在代码生成的过程中,可以

  1. 通过addNewFunction 方法添加函数,

  2. 通过 declareAddedFunctions 方法声明函数。

其他方法

  1. 类型为 Seq[ExprCode]的currentVars,用来记录生成的各列作为当前算子的输入;

  2. freshName 方法与类型为 HashMap[String,Int]的 freshNameids 配合,用来生成具有唯一 ID 的变量名。

  3. nullSafeExec 可以对通常的代码添加 null检测的逻辑。

CodeGenerator

代码生成的过程由代码生成器(CodeGenerator)完成, CodeGenerator 是一个基类,对外提供生成代码的接口是 generate。其含有六个子类:
在这里插入图片描述

generate

在这里插入图片描述
使用bind方法将表达式的和inputSchema进行绑定(一般会调用BindReferences.bindReference方法)。再调用generate(expressions: InType)方法。
在这里插入图片描述
generate(expressions: InType)会调用canonicalize对表达式进行规范化,然后调用create方法进行代码生成。

CodeGenerator子类重写了bind方法、canonicalize方法、create方法以实现自己的代码生成逻辑。

create方法中会得到最终的Java代码字符串,需要使用 CodeGenerator 其伴生对象提供的 compile 方法进行编译,得到 GeneratedClass 的子类。 GeneratedClass 仅仅起到封装生成类的作用,然后会调用 GeneratedClass.generate 方法显示地强制转换得到生成的类。所以CodeGenerator.generate最后返回由表达式生成的类

在这里插入图片描述

表达式代码生成实例

为了考察基本的代码生成功能,需要在 Spark 中关闭全阶段 代码生成,即将 spark.sql.codegen.wholeStage 设置为 false。

SparkSQL:select name from student where age > 18 ;
在这里插入图片描述
由上述SQL表达式经过语法解析、逻辑计划解析、物理计划的解析最终生成逻辑算子树。从各个不同节点的 doExecute出方法可知,执行计划中的 FilterExec 节点会调用 GeneratePredicate 对象的 generate 方法生成 Predicate 类,完成过滤算子的逻辑; ProjectExec 节点则会调用 GenerateUnsafeProjection 对象的 generate 方法生成 UnsafeProjection 类,完成投影算子的逻辑。

GeneratePredicate

由上述内容可知,调用GeneratePredicate的generate(expressions: InType, inputSchema: Seq[Attribute])方法,其调用嵌套为:
create(canonicalize(bind(expressions, inputSchema)))
会依次调用bind方法、canonicalize方法、create方法。

bind(GeneratePredicate)

GeneratePredicate重写了bind方法:
在这里插入图片描述
在这里插入图片描述
BindReferences.bindReference根据表达式输入属性inputSchema,将表达式expression以及其子节点中的所有AttributeReference转换为BoundReference。BoundReference也是一种表达式类型,其记录了属性在输入行中的位置(ordinal),类型(dataType)和是否可以为null的信息。

在本例中即将age属性转换为BoundReference。

create(GeneratePredicate)

create中传入的是经过绑定(bind)和规范化(canonicalize)的表达式。
在这里插入图片描述
代码生成先构造一个 CodegenContext对象,然后 FilterExec 算子的谓词表达式 predicate 接调用其内部的 genCode 方法(最终对应 doGenCode 方法),其返回一个ExprCode对象。
在这里插入图片描述
然后将表达式生成的ExprCode对象和代码上下文CodeGenContext中的信息添加入codeBody字符串。其中eval函数就是具体用来计算表达式的函数,函数体中添加的是ExprCode中的code对象(即java代码字符串),函数返回值添加的是ExprCode中的isNull和value字符串。
在这里插入图片描述
最后对字符串进行编译和生成,得到表达式生成的类,进行返回。

genCode(Expression)

在这里插入图片描述

首先在代码上下文ctx上为isNull和value声明一个唯一的名称(例如”isNull1,value1“)。
在这里插入图片描述
然后调用expression的doGenCode进行实际的代码生成,并传入代码上下文ctx,和ExprCode(
JavaCode.isNullVariable(isNull),
JavaCode.variable(value, dataType))。

JavaCode.isNullVariable(isNull)生成javaClass为BooleanType,name为isNull的局部变量(ExprValue类型)。JavaCode.variable(value, dataType)函数是生成name为value,javaClass为dataType的局部变量(ExprValue类型)。

在这里插入图片描述在这里插入图片描述

doGenCode(Expression)

一般表达式的子列都会重写doGenCode函数来实现自己的代码生成逻辑。下面是And表达式的doGenCode代码:
在这里插入图片描述

  1. 首先会对左右子节点进行代码生成,得到左右子节点生成的ExprCode对象eval1和eval2。
  2. 然后使用ExprCode.copy方法为ev生成code代码(具体的计算逻辑)。
  3. 如果左右子表达式都不为null,则先添加eval1的code(${eval1.code}),然后声明返回值对象且初始值为false(boolean ${ev.value} = false;),如果eval1的返回值为true,则添加eval2的code代码(${eval2.code}),并将eval2的返回值赋给自己的返回值对象(${ev.value} = ${eval2.value};)。然后将ev的isNull 赋值为FalseLiteral,表示返回的结果不为null。
  4. 如果左右子表达式有可能为null。code代码的生成逻辑上是差不多的。只是这里多声明了一个表示自己返回值是否为null的变量(boolean ${ev.isNull} = false;),以及增加了对返回值是否为null 的判断。

综上我们可以看出doGenCode的主要任务是生成ExprCode。首先将其子表达式生成的ExprCode的code添加到自己code中,并利用子ExprCode的isNull变量和返回值变量进行逻辑判断操作,生成自己的code逻辑过程,最后得到自己的isNull变量和返回值变量的结果

doGenCode整个过程类似于字符串拼接

code只是一段代码块,不需要写return语句。具体的返回值操作在GeneratePredicate.create方法中,codeBody中使用了ExprCode的isNull变量和返回值变量作为SpecificPredicate类的eval方法的返回值

在本例中And表达式的子表达式分别为IsNotNull和GreaterThan,下面展示IsNotNull的doGenCode。
在这里插入图片描述
可见其获得子表达式生成的ExprCode,然后创建自己的ExprCode,其并使用子ExprCode的code主体。将isNull赋值为FalseLiteral,表示不可能为null。value赋值为判断子ExprCode的isNull对象为true还是false。

而BoundReference的doGenCode,主要是根据ordinal从InternalRow中取值。
在这里插入图片描述

综上GeneratePredicate最终会生成SpecificPredicate类。
在这里插入图片描述

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java代码可以根据时间来生成cron表达式。参考提供的引用,我们可以使用Java的SimpleDateFormat类来处理时间戳,并将其转换为适用于cron表达式的格式。下面是一个示例代码片段: ```java import java.text.SimpleDateFormat; import java.util.Date; public class CronExpressionGenerator { public static void main(String[] args) { // 获取当前时间戳 long currentTime = System.currentTimeMillis(); // 创建SimpleDateFormat对象,指定时间戳格式 SimpleDateFormat sdf = new SimpleDateFormat("ss mm HH dd MM ? yyyy"); // 将时间戳格式化为cron表达式 String cronExpression = sdf.format(new Date(currentTime)); System.out.println("生成的cron表达式为:" + cronExpression); } } ``` 以上代码将当前时间戳格式化为cron表达式,并打印输出。其中,cron表达式的各个字段含义可以参考提供的引用。此外,根据需要,还可以使用参数来实现更加灵活的时间范围设置,如“6L”表示这个月的倒数第6天,"FRIL"表示这个月的最后一个星期五。具体使用方法可以参考提供的引用。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [cron表达式详解以及用Java自动生成cron表达式](https://blog.csdn.net/qq_43102730/article/details/125288958)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [java cron表达式生成器](https://download.csdn.net/download/duanyichen746/9966400)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值