openGauss源码解析(136)
openGauss源码解析:执行器解析(29)
7.5 编译执行
为了提高SQL的执行速度,解决传统数据处理引擎条件逻辑冗余的问题,openGauss为执行表达式引入了CodeGen技术,其核心思想是为具体的查询生成定制化的机器码代替通用的函数实现,并尽可能地将数据存储在CPU寄存器中。openGauss通过LLVM编译框架来实现CodeGen,LLVM是“Low Level Virtual Machine”的缩写,开发之初是想作为一个底层虚拟机,但随着开发,以及功能的逐渐完善,慢慢变成一个模块化的编译系统,并能支持多种语言。LLVM的系统架构如图7-23所示。
图7-23 LLVM系统架构
LLVM大体上可以分成3个部分。
(1) 支持多种语言的前端。
(2) 优化器。
(3) 支持多种CPU架构的后端(X86、Aarch64)。
LLVM与GCC一样,都是常用的编译系统,但是LLVM更加模块化,从而可以免去每使用一套语言换一套优化器的工作,开发者只要设计相应的前端,并针对各个目标平台做后端优化。
考虑如下SQL语句。
SELECT * FROM dataTable WHRER (x + 2) * 3 > 4;
正常的递归流程如图7-24所示。
图7-24 一般的表达式执行流程
此类表达式的执行代码是一套通用的函数实现,每次递归都有很多冗余判断,需要依赖上一步的输出作为当前的输入,实现如下代码逻辑:
void MaterializeTuple(char * tuple) {
for (int I = 0; i < num_slots_; i++) {
char* slot = tuple + offsets_[i];
switch(types_[i]) {
case BOOLEAN:
*slot = ParseBoolean();
break;
case INT:
*slot = ParseInt();
Break;
case FLOAT: …
case STRING: …
… …
}
}
}
通过CodeGen可以为表达式构造定制化的实现,如下代码所示:
void MaterializeTuple(char * tuple) {
*(tuple + 0) = ParseInt();
*(tuple + 4) = ParseBoolean();
*(tuple + 5) = ParseInt();
}
通过减少冗余的判断分支,极大减少了SQL执行时间,同时也减少大量虚函数的调用。为了实现基于LLVM的CodeGen,并方便接口调用,openGauss定义了一个GsGodeGen类,GodeGen所有接口都在这个类中实现,主要的成员变量包括:
llvm::Module* m_currentModule; /* 当前query使用的module */
bool m_optimizations_enabled; /* modules是否能优化 */
bool m_llvmIRLoaded; /* IR文件是否已经载入 */
bool m_isCorrupt; /* 当前query的module是否可用 */
bool m_initialized; /* GsCodeGen 对象是否完成初始化 */
llvm::LLVMContext* m_llvmContext; /* llvm上下文 */
List* m_machineCodeJitCompiled; /* 保存所有机器码JIT编译完成的函数 */
llvm::ExecutionEngine* m_currentEngine; /* 当前query的llvm执行引擎 */
bool m_moduleCompiled; /* module是否编译完成 */
MemoryContext m_codeGenContext; /* CodeGen内存上下文 */
List* m_cfunction_calls; /* 记录表达式中调用IR的c函数 */
这里涉及一些LLVM的概念。Module是LLVM的一个重要类,可以把Module看作一个容器,每个Moudle以下的元素构成:函数、全局变量、符号表入口、以及LLVM linker(联系Moudles之间其他模块的全局变量,函数的前向声明,以及外部符号表入口);LLVMContext这是一个在线程上下文中使用LLVM的类。它拥有和管理LLVM核心基础设施的核心“全局”数据,包括类型和常量唯一表。IR文件是LLVM的中间文件,前端将用户代码(C/C++、python等)转换成IR文件,优化器对IR文件进行优化。openGauss的GodeGen代码功能之一就是将函数转换成IR格式的文件。通常在代码中将源代码转换成IR的方式有多种,openGauss生成IR是使用“llvm::IRBuilder<>”函数,在后面会详细介绍。如果查询计划树的算子支持CodeGen,那么针对该函数生成“Intermediate Representation”函数(IR 函数)。这个IR函数是查询级别的,即每一个查询对应的IR函数是不同的。同时对应每一个查询有多个IR函数,这是因为可以只做局部替换,即只动态生成查询计划树中某个算子或某部分操作函数的IR函数,如只实现投影功能的IR函数。