openGauss数据库源码解析系列文章—— 执行器解析(二)_param_extern param_exec param_sublink(2)

还有兄弟不知道网络安全面试可以提前刷题吗?费时一周整理的160+网络安全面试题,金九银十,做网络安全面试里的显眼包!

王岚嵚工程师面试题(附答案),只能帮兄弟们到这儿了!如果你能答对70%,找一个安全工作,问题不大。

对于有1-3年工作经验,想要跳槽的朋友来说,也是很好的温习资料!

【完整版领取方式在文末!!】

93道网络安全面试题

内容实在太多,不一一截图了

黑客学习资源推荐

最后给大家分享一份全套的网络安全学习资料,给那些想学习 网络安全的小伙伴们一点帮助!

对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。

😝朋友们如果有需要的话,可以联系领取~

1️⃣零基础入门
① 学习路线

对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。

image

② 路线对应学习视频

同时每个成长路线对应的板块都有配套的视频提供:

image-20231025112050764

2️⃣视频配套工具&国内外网安书籍、文档
① 工具

② 视频

image1

③ 书籍

image2

资源较为敏感,未展示全面,需要的最下面获取

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

② 简历模板

在这里插入图片描述

因篇幅有限,资料较为敏感仅展示部分资料,添加上方即可获取👆

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以点击这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

主要函数说明
ExecMakeFunctionResultNoSets表达式计算(非集合)
ExecMakeFunctionResult表达式计算(集合)
ExecEvalFunc/ExecEvalOper调用表达式计算函数
ExecQual检查条件表达式
ExecEvalOr处理or表达式
ExecTargetList计算targetlist中的所有表达式
ExecProject计算投影信息
ExecEvalParamExec获取Exec类型参数
ExecEvalParamExtern获取Extern类型参数

ExecMakeFunctionResult函数和ExecMakeFunctionResultNoS函数是表达式计算的核心函数,主要作用是通过获取表达式的参数来计算出表达式结果。ExecMakeFunctionResultNoSets函数是ExecMakeFunctionResult函数的简化版,只能处理返回值是非集合情况。ExecMakeFunctionResult函数核心代码如下:

fcinfo = &fcache->fcinfo_data;                           /* 声明fcinfo */
InitFunctionCallInfoArgs(*fcinfo, list_length(fcache->args), 1); /*初始化fcinfo */ 
econtext->is_cursor = false;
    foreach (arg, fcache->args) {                          /* 遍历获取参数值 */
        ExprState* argstate = (ExprState*)lfirst(arg);
        fcinfo->argTypes[i] = argstate->resultType;
        fcinfo->arg[i] = ExecEvalExpr(argstate, econtext, &fcinfo->argnull[i], NULL);
if (fcache->func.fn_strict)                   /* 判断参数是否存在空值 */
…… 
result = FunctionCallInvoke(fcinfo);           /* 计算表达式结果 */
return result;

ExecMakeFunctionResultNoSets函数的执行流程如下。
(1) 声明fcinfo来存储表达式需要的参数信息,通过InitFunctionCallInfoArgs函数初始化fcinfo中的字段。
(2) 遍历表达式中的参数args,通过ExecEvalExpr宏调用接口获取每一个参数的值,存储到“fcinfo->arg[i]”中。
(3) 根据func.fn_strict函数来判断是否需要检查参数空值情况。如果不需要检查,则通过“FunctionCalllv-oke”宏将参数传入表达式并计算出表达式的结果。否则进行判空处理,若存在空值则直接返回空,若不存在空值则通过FunctionCalllvoke宏计算表达式结果。
(4) 返回计算结果。
流程如图7-14所示。
在这里插入图片描述

图7-14 “ExecMakeFunctionResultNoSets”函数执行流程

ExecMakeFunctionResult函数的执行流程如图7-15所示。
(1) 判断funcResultStore是否存在,如果存在则从中获取结果返回(注:如果下文(3)中的模式是SFRM_Materialize,则会直接跳到此处)。
(2) 计算出参数值存入到fcinfo中。
(3) 把参数传入到表达式函数中计算表达式,首先判断参数args是否存在空,然后判断返回集合的函数的返回模式,SFRM_ValuePerCall模式是每次调用返回一个值,SFRM_Materialize模式是在Tuplestore中实例化的结果集。
(4) 根据不同的模式进行计算并返回结果。
在这里插入图片描述

图7-15 ExecMakeFunctionResult函数执行流程

ExecEvalFunc和ExecEvalOper这两个函数的功能类似。通过调用结果处理函数来获取结果。如果函数本身或者它的任何输入参数都可以返回一个集合,那么就会调ExecMakeFunctionResult函数来计算结果,否则调用ExecMakeFunctionResultNoSets函数来计算结果。核心代码如下:

init_fcache<false>(func->funcid,func->inputcollid,fcache, econtext->ecxt_per_query_memory, true);                 /* 初始化fcache */
if (fcache->func.fn_retset) {                           /* 判断返回结果类型 */
    ……
return ExecMakeFunctionResult<true, true, true>(fcache, econtext, isNull, isDone);
} else if (expression_returns_set((Node\*)func->args)) {
……
return ExecMakeFunctionResult<true, true, false>(fcache, econtext, isNull, isDone);
} else {
……
return ExecMakeFunctionResultNoSets<true, true>(fcache, econtext, isNull, isDone);
}

ExecEvalFunc函数的执行流程如下。
(1) 是通过init_fcache函数初始化FuncExprState节点,包括初始化参数、内存管理等等。
(2) 根据FuncExprState函数中的数据判断返回结果是否为set类型,并调用相应的函数计算结果。
ExecEvalFunc函数执行流程如图7-16所示。
在这里插入图片描述

图7-16 ExecEvalFunc函数执行流程

ExecQual函数的作用是检查slot结果是否满足表达式中的子表达式,如果子表达式为false,则返回false否则返回true,表示该结果符合预期,需要输出。核心代码如下:

foreach (l, qual) {        /* 遍历qual中的子表达式并计算 */
expr_value = ExecEvalExpr(clause, econtext, &isNull, NULL);
if (isNull) {  /* 判断计算结果 */
if (resultForNull == false) {
result = false; 
break;
}
        } else {
            if (!DatumGetBool(expr_value)) {
                  result = false;
 ……
 return result;   /* 返回结果是否满足表达式 */

ExecQual函数的主要执行流程如下。
(1) 遍历qual中的子表达式,根据ExecEvalExpr函数计算结果是否满足该子表达式,若满足则expr_value为1,否则为0。
(2) 判断结果是否为空,若为空,则根据resultForNull参数得到返回值信息。若不为空,则根据expr_value判断返回true或者false。
(3) 返回result。
ExecQual函数的执行流程如图7-17所示。
在这里插入图片描述

图7-17 ExecQual函数执行流程

ExecEvalOr函数的作用是计算通过or连接的bool表达式(布尔表达式,最终只有true(真)和false(假)两个取值),检查slot结果是否满足表达式中的or表达式。如果结果符合or表达式中的任何一个子表达式,则直接返回true,否则返回false。如果获取的结果为null,则记录isNull为true。核心代码如下:

foreach (clause, clauses) {              /* 遍历子表达式 */
        ExprState* clausestate = (ExprState*)lfirst(clause);
        Datum clause_value;
        clause_value = ExecEvalExpr(clausestate, econtext, isNull, NULL);  /* 执行表达式 */
        /* 如果得到不空且ture的结果,直接返回结果 */
if (*isNull) 
/* 记录存在空值 */
            AnyNull = true; 
        else if (DatumGetBool(clause_value))
/* 一次结果为true就返回 */
            return clause_value;  /* 返回执行结果 */
    }
*isNull = AnyNull;
return BoolGetDatum(false);

ExecEvalOr函数主要执行流程如下。
(1) 遍历子表达式clauses。
(2) 通过ExecEvalExpr函数来调用clause中的表达式计算函数,计算出结果。
(3) 对结果进行判断,or表达式中若有一个结果满足条件,就会跳出循环直接返回。
ExecEvalOr函数的执行流程如图7-18所示。
在这里插入图片描述

图7-18 ExecEvalOr函数执行流程

ExecTargetList函数的作用是根据给定的表达式上下文计算targetlist中的所有表达式,将计算结果存储到元组中。主要结构体代码如下:

typedef struct GenericExprState {
    ExprState xprstate;
    ExprState* arg; /*子节点的状态*/
} GenericExprState;
typedef struct TargetEntry {
    Expr xpr;
    Expr* expr;            /*要计算的表达式*/
    AttrNumber resno;      /*属性号*/
    char* resname;         /*列的名称*/
    Index ressortgroupref;    /*如果被sort/group子句引用,则为非零*/
    Oid resorigtbl;           /*列的源表的OID */
    AttrNumber resorigcol;    /*源表中的列号*/
    bool resjunk;            /*设置为true可从最终目标列表中删除该属性*/
} TargetEntry;

ExecTargetList函数主要执行流程如下。
(1) 遍历targetlist中的表达式。
(2) 计算表达式结果。
(3) 判断结果中itemIsDone[resind]参数并生成最后的元组。
ExecTargetList函数的执行流程如图7-19所示。
在这里插入图片描述

图7-19 ExecTargetList函数执行流程

ExecProject函数的作用是进行投影操作,投影操作是一种属性过滤过程,该操作将对元组的属性进行精简,把在上层计划节点中不需要用的属性从元组中去掉,从而构造一个精简版的元组。投影操作中被保留下来的那些属性被称为投影属性。主要结构体代码如下:

typedef struct ProjectionInfo {
    NodeTag type;
    List* pi_targetlist;            /*目标列表*/
    ExprContext* pi_exprContext;  /*内存上下文*/
    TupleTableSlot* pi_slot;       /*投影结果*/
    ExprDoneCond* pi_itemIsDone; /*ExecProject的工作区数组*/
    bool pi_directMap;
    int pi_numSimpleVars;    /*在原始tlist(查询目标列表)中找到的简单变量数*/
    int* pi_varSlotOffsets;    /*指示变量来自哪个slot(槽位)的数组*/
    int* pi_varNumbers;     /*包含变量的输入属性数的数组*/
    int* pi_varOutputCols;   /*包含变量的输出属性数的数组*/
    int pi_lastInnerVar;      /*内部参数*/
    int pi_lastOuterVar;     /*外部参数*/
    int pi_lastScanVar;      /*扫描参数*/
    List* pi_acessedVarNumbers;
    List* pi_sysAttrList;
    List* pi_lateAceessVarNumbers;
    List* pi_maxOrmin;    /*列表优化,指示获取此列的最大值还是最小值*/
    List* pi_PackTCopyVars;            /*记录需要移动的列*/
    List* pi_PackLateAccessVarNumbers;  /*记录cstore(列存储)扫描中移动的内容的列*/
    bool pi_const;
    VectorBatch* pi_batch;
    vectarget_func jitted_vectarget;      /* LLVM函数指针*/
    VectorBatch* pi_setFuncBatch;
} ProjectionInfo;

ExecProject函数的主要执行流程如下。
(1) 取ProjectionInfo需要投影的信息。按照执行的偏移获取原属性所在的元组,通过偏移量获取该属性,并通过目标属性的序号找到对应的新元组属性位置进行赋值。
(2) 对pi_targetlist进行运算,将结果赋值给对应元组中的属性。
(3)产生一个行记录结果,对slot做标记处理,slot包含一个有效的虚拟元组。
ExecProject函数的执行流程如图7-20所示。
在这里插入图片描述

图7-20 ExecProject函数执行流程

ExecEvalParamExec函数的作用是获取并返回PARAM_EXEC类型的参数。PARAM_EXEC参数是指内部执行器参数,是需要执行子计划来获取的结果,最后需要将结果返回到上层计划中。核心代码如下:

prm = &(econtext->ecxt_param_exec_vals[thisParamId]); /* 获取econtext中参数 */
if (prm->execPlan != NULL) {                    /* 判断是否需要生成参数 */
  /* 参数还未计算执行此函数*/
  ExecSetParamPlan((SubPlanState*)prm->execPlan, econtext);
  /*参数计算完计划重置为空*/
  Assert(prm->execPlan == NULL);
  prm->isConst = true;
  prm->valueType = expression->paramtype;
}
*isNull = prm->isnull;
prm->isChanged = true;
return prm->value;                               /* 返回生成的参数 */

ExecEvalParamExec函数的主要执行流程如下。
(1) 获取econtext中的ecxt_param_exec_vals参数。
(2) 判断子计划是否为空,若不为空则调用ExecSetParamPlan函数执行子计划获取结果,并把计划置为空,当再次执行此函数时,不需要重新执行计划,直接返回已经获取过结果。
(3) 将结果prm->value返回。
ExecEvalParamExec函数的执行流程如图7-21所示。
在这里插入图片描述

图7-21 ExecEvalParamExec函数执行流程

ExecEvalParamExtern函数的作用是获取并返回PARAM_EXTERN类型的参数。该参数是指外部传入参数,例如在PBE执行时,PREPARE的语句中的参数,在需要execute语句执行时传入。核心代码如下:

if (paramInfo && thisParamId > 0 && thisParamId <= paramInfo->numParams) {/* 判断参数 */
ParamExternData* prm = &paramInfo->params[thisParamId - 1];
  if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL)   /* 获取动态参数 */ 
    (*paramInfo->paramFetch)(paramInfo, thisParamId);
    if (OidIsValid(prm->ptype)) {                               /*检查参数并返回 */ 
if (prm->ptype != expression->paramtype)
ereport(……);
       *isNull = prm->isnull;
       if (econtext->is_cursor && prm->ptype == REFCURSOROID) {
         CopyCursorInfoData(&econtext->cursor_data, &prm->cursor_data);
         econtext->dno = thisParamId - 1;
       }
       return prm->value;
   }
}
  ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("no value found for parameter %d", thisParamId)));
  return (Datum)0;

ExecEvalParamExtern函数主要执行流程如下。
(1) 判断PARAM_EXTERN类型的参数否存在,若存在则从ecxt_param_list_info中获取该参数,否则直接报错。
(2) 判断参数是否是动态的,若是动态的则再次获取参数。
(3) 判断参数类型是否符合要求,若符合要求直接返回该参数。
ExecEvalParamExtern函数的执行流程如图7-22所示。
在这里插入图片描述

图7-22 ExecEvalParamExtern函数执行流程

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函数。

openGauss GodeGen的整体编译流程如图7-25所示。
在这里插入图片描述

图7-25 openGauss CodeGen编译执行流程

数据库启动后,首先对LLVM初始化,其中CodeGenProcessInitialize函数对LLVM的环境进行初始化,包括通过isCPUFeatureSupportCodegen函数和canInitCodegenInvironment函数检查CPU是否支持CodeGen、是否能够进行环境初始化。然后通过“GsCodeGen::InitializeLlvm”函数对本地环境检查,检查环境是否为Aarch64或x86架构,并返回全局变量gscodegen_initialized。
CodeGenThreadInitialize函数在本线程中创建一个新的GsCodeGen对象,并创建内存。如果创建失败,要返回原来的内存上下文给系统,当前线程中codegen的部分保存在knl_t_codegen_context中,具体结构代码为:

typedef struct knl_t_codegen_context {
    void* thr_codegen_obj;
    bool g_runningInFmgr;
    long codegen_IRload_thr_count;
} knl_t_codegen_context;

其中thr_codegen_obj字段保存代码中LLVM对象,在初始化和调用时通常转换成GsCodeGen类,GsCodeGen保存了LLVM全部封装好的LLVM函数、内存和成员变量等。g_runningInFmgr字段表示函数是否运行在function manager中。codegen_IRload_thr_count字段是IR载入计数。
当所有的LLVM执行环境设置完成后,执行器初始化阶段可根据解析器和优化器提供的查询计划去检查当前的计划是否可以进行LLVM代码生成优化。以gsql客户端为例,整个运行过程内嵌在执行引擎运行过程内,函数的调用从函数exec_simple_plan函数为入口,LLVM运行的3个阶段分别对应executor的3个阶段:ExecutorStart、ExecutorRun以及ExecutorEnd(从其他客户端输入的查询,最终也会走到ExecutorStart、ExecutorRun以及ExecutorEnd阶段)。
(1) ExecutorStart阶段:为运行准备阶段,初始化查询级别的GsCodeGen类对象,并在InitPlan阶段按照优化器产生的执行计划遍历其中各个算子节点初始化函数,生成IR函数。
(2) ExecutorRun阶段:为运行阶段,若已成功生成LLVM IR函数,则对该IR函数进行编译,生成可执行的机器码,并在具体的算子运行阶段用机器码替换到原本的执行函数入口。
(3) ExecutorEnd阶段:为运行完清理环境阶段,在ExecutorEnd函数中将第一阶段生成的LLVMCodeGen对象及其相关资源进行释放。
GsCodeGen的接口定义在文件“codegen/gscodegen.h”中,GsCodeGen中接口说明如表7-31所示。

表7-31 GsCodeGen接口汇总

接口名称接口类型职责描述
initializeAPI分配Codegen使用内存使用环境
InitializeLLVMAPI初始化LLVM运行环境
parseIRFileAPI解析IR文件
cleanupLlvmAPI停止LLVM调用线程
createNewModuleAPI创建一个新的LLVM模板
compileCurrentModuleAPI编译当前指定LLVM模块中的函数
compileModuleAPI编译模板并依据相关选项对模板中未用的IR函数进行优化
releaseResourceAPI释放LLVM模块占用的系统资源
FinalizeFunctionAPI确定最后的IR函数是否可用
getTypeAPI从openGauss的类型转换到LLVM内部对应的类型
verifyFunctionAPI检查输入的LLVM IR函数的有效性
getPtrTypeAPI从openGauss的类型转换到LLVM内部对应该类型的指针类型
castPtrToLlvmPtrAPI将openGauss的指针转换为LLVM的指针
getIntConstantAPI将openGauss对应类型的常数转换为LLVM对应类型的常数
generatePrototypeAPI创建要加入当前LLVM模块的函数原型
replaceCallSitesAPI替换LLVM当前模块的函数
optimizeModuleAPI优化LLVM当前模块中的函数
addFunctionToMCJitAPI外部函数调用接口
canInitCodegenInvironmentAPI判断当前可否初始化CodeGen环境
canInitThreadCodeGenAPI判断当前可否初始化CodeGen线程
CodeGenReleaseResourceAPI删除当前模板和LLVM执行引擎
CodeGenProcessInitializeAPI初始化LLVM服务进程
CodeGenThreadInitilizeAPI初始化LLVM服务线程
CodeGenThreadRuntimeSetupAPI初始化LLVM服务对象
CodeGenThreadRuntimeCodeGenerateAPI编译当前LLVM模板中的IR函数
CodeGenThreadTearDownAPI释放LLVM模块占用的系统资源接口
CodeGenThreadObjectReadyAPI判断当前LLVM服务对象是否有效
CodeGenThreadResetAPI清空当前内存中的机器码
CodeGenPassThresholdAPI根据返回行数判断是否需要CodeGen

GsCodeGen提供LLVM环境处理函数和module函数,以及处理IR的函数。另一方面,为了处理算子函数功能,将每个算子涉及的各个操作符封装在ForeigenScanCodeGen类中,接口定义在“codegen/foreignscancodegen.h”中,各个接口功能如表7-32所示:

表7-32 ForeigenScanCodeGen接口汇总

接口名称接口类型职责描述
ScanCodeGenAPI生成外表扫描谓词表达式运算对应的IR函数
IsJittableExprAPI谓词中的表达式是否支持LLVM化
buildConstValueAPI获取谓词表达式中的常量

目前针对不同的表达式,openGauss实现了4个类:
(1) VecExprCodeGen类主要用于处理查询语句中表达式计算的LLVM动态编译优化。目前主要处理的是过滤条件语法中的表达式,即在ExecVecQual函数中处理的表达式计算。
(2) VecHashAggCodeGen类用于对节点hashagg运算的LLVM动态编译优化。
(3) VecHashJoinCodeGen类用于对节点hash join运算的LLVM动态编译优化。
(4) VecSortCodeGen类用于对节点sort运算的LLVM动态编译优化。

7.5.1 VecExprCode类

VecExprCodeGen类用于支持openGauss设计框架中向量化表达式的动态编译优化,即生成各类向量化表达式计算的IR函数。VecExprCodeGen类主要针对存在qual的查询场景,即表达式在WHERE语法中的查询场景,VecExprCodeGen接口定义在“codegen/vecexprcodegen.h”文件中,VecExprCode类支持的语句场景为:

SELECT targetlist expr FROM table WHERE filter expr…;

其中,对filter expr进行LLVM化处理。
列存储执行引擎每次处理的为一个VectorBatch。在执行过程中,由于采用迭代计算模型,对于每一个qual,会遍历整个qual表达式,然后根据遍历得到的信息去读取VectorBatch中的列向量ScalarVector,这样就会导致需要不停地去替换当前存放在内存或寄存器中的数据。为了更好地减少数据读取,让数据在计算过程中更久地存放在寄存器中,将ExecVecQual与对VectorBatch进行结合处理:只有当前的数据处理完所有的vecqual时再更新寄存器中的数据,即原本的执行流程。相关代码如下:

foreach(cell, qual)
{
DealVecQual(batch->m_arr[var->attno-1]);
}
替换为
for(i = 0; i < batch->m_rows; i++)
{
foreach(cell, qual)
{
DealVecQual(batch->m_arr[var->attno-1]->m_vals[i]);
}
}

DealVecQual代表的就是对当前的数据参数进行qual条件处理。可以看到现有的处理方式实际上已经退化为行存储的形式,即每次只处理batch中的一行数据信息,但是该数据信息会一直存放在寄存器中,直至所有的qual条件处理完成。表7-33列出了VecExprCodeGen的所有接口。

表7-33 VecExprCodeGen接口汇总

接口名称接口类型职责描述
ExprJittableAPI判断单个表达式是否支持LLVM化
QualJittableAPI判断整个qual条件是否支持LLVM化
QualCodeGenAPIExecVecQual的LLVM化,生成的“machine code”用于替换实际执行时的ExecVecQual
ExprCodeGenAPIExecInitExpr的LLVM化,目前只支持部分功能和函数的LLVM化
OpCodeGenAPI操作符表达式(算术表达式,比较表达式等)的LLVM化,目前支持的数据类型包括int、float、numeric、text和bpchar等类型
ScalarArrayCodeGenAPIExecEvalScalarArrayOp的LLVM化处理,支持的类型包括text、varchar、bpchar、int和float类型
CaseCodeGenAPIExecEvalVecCase的LLVM化处理,其中“case when”中的选项类型包括int类型和text、bpchar类型,对于复杂表达式的暂只支持substr
VarCodeGenAPIExecEvalVecVar的LLVM化处理
EvalConstCodeGenAPIExecEvalConst的LLVM化处理

举例来说,以ExecCStoreScan函数中处理qual表达式来说明,以本次查询所生成的查询计划树为输入,编译得到机器码。因此实现调用需要做到如下两点。

(1) 结合所实现的函数接口,依据当前查询计划树,生成对应的IR函数。
如提供了ExecVecQual的LLVM化接口,则通过遍历每一个qual并判断是否支持LLVM化来判断当前的ps.qual是否可生成IR函数。如果判断可生成,则借助IR builder API生成对应于当前quallist的IR函数,相关代码如下:

if (!codegen_in_up_level) {
consider_codegen = CodeGenThreadObjectReady() &&
CodeGenPassThreshold(((Plan\*)node)->plan\_rows,
estate->es\_plannedstmt->num\_nodes, ((Plan\*)node)->dop);
 if (consider\_codegen) {
 jitted\_vecqual = dorado::VecExprCodeGen::QualCodeGen(scan\_stat->ps.qual, (PlanState\*)scan\_stat);
 if (jitted\_vecqual != NULL)
 llvm\_code\_gen->addFunctionToMCJit(jitted\_vecqual, reinterpret\_cast<void\*\*>(&(scan\_stat->jitted\_vecqual)));
    }
}

代码段显示了ExecInitCStoreScan函数中对于ps.qual部分的处理。如果存在LLVM环境,则优先去生成ps.qual的IR函数。在QualCodeGen函数中的QualJittable用于判断当前ps.qual是否可LLVM化。
(2) 将原本的执行函数入口替换成预编译好的可执行机器码。
当步骤(1)已经生成IR函数后,则根据如图7-25中所示那样会进行编译(compile IR Function)。那么在实际执行过滤的时候就会进行替换。相关代码如下:

if (node->jitted_vecqual)
p_vector = node->jitted_vecqual(econtext);
else
p_vector = ExecVecQual(qual, econtext, false);

代码段显示了如果生成了用于处理CStoreScan函数中plan.qual的机器码,则直接去调用生成的jitted_vecqual函数。如果没有,则按照原有的执行逻辑去处理。

表7-33中提到OpCodegen是操作符的LLVM化,其支持的数据结构包括了布尔型、浮点型、整型和字符型等,源代码在“gausskernel/runtime/codegen/codegenutil”目录中,以boolcodegen.cpp、datecocegen.cpp格式命名。

LLVM提供了很多针对于数据的基本操作,包括基本算术运算和比较运算。由于LLVM最高可支持(223-1)位的整数类型,且数据类型可以进行二进制转换(延展,扩充都可以),因此LLVM只需要提供整型数据比较和浮点型数据比较即可。一个典型的比较运算符接口代码如下(以’=’为例):

llvm:Value *CreateICmpEQ(Value *LHS, Value *RHS, const Twine &Name = "")

其中LHS和RHS为参与运算的输入参数,而Name表示在运算时候的变量名。类似地,LLVM也提供了众多的基本运算,如两个整型数据相加的接口代码为:



### 一、网安学习成长路线图


网安所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/aa7be04dc8684d7ea43acc0151aebbf1.png)


### 二、网安视频合集


观看零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/f0aeee2eec7a48f4ad7d083932cb095d.png)


### 三、精品网安学习书籍


当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/078ea1d4cda342f496f9276a4cda5fcf.png)


### 四、网络安全源码合集+工具包


光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/e54c0bac8f3049928b488dc1e5080fc5.png)


### 五、网络安全面试题


最后就是大家最关心的网络安全面试题板块  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/15c1192cad414044b4dd41f3df44433d.png)![在这里插入图片描述](https://img-blog.csdnimg.cn/b07abbfab1fd4edc800d7db3eabb956e.png)  



**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化资料的朋友,可以点击这里获取](https://bbs.csdn.net/topics/618540462)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值