4

openGauss数据库源码解析系列文章—— 执行器解析(三)

 2 years ago
source link: https://my.oschina.net/gaussdb/blog/5207330
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

上一篇介绍了第七章执行器解析中“7.4 表达式计算”及“7.5 编译执行”的相关内容,本篇将介绍“7.6 向量化引擎”及“7.7 小结”的精彩内容。

7.6 向量化引擎

传统的行执行引擎大多采用一次一元组的执行模式,这样在执行过程中CPU大部分时间并没有用来处理数据,更多的是在遍历执行树,就会导致CPU的有效利用率较低。而在面对OLAP场景巨量的函数调用次数,需要巨大的开销。为了解决这一问题,openGauss中增加了向量化引擎。向量化引擎使用了一次一批元组的执行模式,能够大大减少遍历执行节点的开销。一次一批元组的数据运载方式也为某些表达式计算的SIMD(single instruction, multiple data,单指令多数据)化提供了机会,SIMD化能够带来性能上的提升。同时向量化引擎还天然对接列存储,能够较为方便地在底层扫描节点装填向量化的列数据。
向量化引擎的执行算子类似于行执行引擎,包含控制算子、扫描算子、物化算子和连接算子。同样会使用节点表示,继承于行执行节点,执行流程采用递归方式。主要包含的节点有:CStoreScan(顺序扫描),CStoreIndexScan(索引扫描),CStoreIndexHeapScan(利用Bitmap获取元组),VecMaterial(物化),VecSort(排序),VecHashJoin(向量化哈希连接)等,下面将逐一介绍这些执行算子。

7.6.1 控制算子

1. VecResult算子

VecResult算子用于处理只有一个结果返回或WHERE过滤条件为常量的情况,对应的代码源文件是“vecresult.cpp”;对应的主要数据结构是VecResult,VecResult继承于BaseResult。VecResult算子相关的函数包括ExecInitVecResult(初始化节点)、ExecVecResult(执行节点)、ExecReScanVecResult(重置节点)、ExecEndVecResult(退出节点)。
ExecInitVecResult函数用于初始化VecResult执行算子。执行流程如图7-28所示,主要执行流程如下。
(1) 创建并初始化VecResult执行节点,并为节点创建表达式上下文。
(2) 调用“ExecInitResultTupleSlot(estate, &res_state->ps)”函数分配存储投影结果的slot。
(3) 调用投影表达式初始化函数ExecInitVecExpr依次对ps.targetlist、ps.qual和resconstantqual进行初始化。
(4) 分别调用ExecAssignResultTypeFromTL函数和ExecAssignVectorForExprEval函数进行扫描描述符的初始化和投影结构的创建。
《》.
在这里插入图片描述

图7-28 ExecInitVecResult函数执行流程

ExecVecResult函数是执行VecResult的主体函数。执行流程如图7-29所示,主要执行流程如下。
(1) 检查是否需要计算常量表达式。
(2) 若需要则重新计算表达式,设置检查标识(如果常量计算表达式结果为false时,则设置约束检查标识位)。
(3) 获取结果元组。
在这里插入图片描述

图7-29 ExecVecResult函数执行流程

ExecReScanVecResult函数用于重新执行扫描计划。
ExecEndVecResult函数用于在执行结束时释放执行过程中申请的相关资源(包括存储空间等)。

2. VectorModifyTable算子

VecModifyTable算子用于处理INSERT、UPDATE、DELETE操作,对应的代码源文件是“vecmodifytable.cpp”;对应的主要数据结构是VecModifyTableState,VecModifyTableState继承于ModifyTableState。具体定义代码如下所示:

typedef struct VecModifyTableState : public ModifyTableState {
    VectorBatch* m_pScanBatch;      /* 工作元组 */
    VectorBatch* m_pCurrentBatch;  /* 输出元组 */
} VecModifyTableState;

VecModifyTable算子相关的函数包括ExecInitVecModifyTable(初始化节点)、ExecVecModifyTable(执行节点)、ExecEndVecModifyTable(退出节点)。
ExecInitVecModifyTable函数用于初始化VecModifyTable算子,调用ExecInitModifyTable函数实现算子的初始化。
ExecVecModifyTable函数是执行VecModifyTable算子的主体函数,循环地从子计划中获取目标列并根据要求修改每一列,通过“switch(operation)”处理不同的修改操作,具体的修改操作包括CMD_INSERT(插入)、CMD_DELETE(删除)、CMD_UPDATE(更新)。
ExecEndVecModifyTable函数用于在执行VecModifyTable算子结束时调用ExecEndModifyTable函数清除相关资源。

3. VecAppend算子

VecAppend算子用于处理包含一个或多个子计划的链表,通过遍历子计划链表逐个执行子计划,对应的代码源文件是“vecappend.cpp”;对应的主要数据结构是VecAppendState,VecAppendState继承于AppendState。
VecAppend算子相关的函数包括ExecInitVecAppend(初始化节点)、ExecVecAppend(执行节点)、ExecReScanAppend(重置节点)、ExecEndVecAppend(退出节点)。
ExecInitVecAppend函数用于初始化VecAppend算子。执行执行流程如图7-30所示,主要执行流程如下。
(1) 创建并初始化执行节点VecAppend。
(2) 分配存储投影结果的slot。
(3) 循环初始化子计划链表。
(4) 初始化扫描描述符并设置初始迭代。在这里插入图片描述

图7-30 ExecInitVecAppend函数执行流程

ExecVecAppend函数是执行VecAppend算子的主体函数。执行流程如图7-31所示,每次从子计划中获取一条元组,当取回全部元组时,移动到下一个子计划,直到执行全部子计划。
在这里插入图片描述

图7-31 ExecVecAppend执行流程

ExecEndVecAppend函数用于在执行结束时清理VecAppend算子,释放相应的子计划。

7.6.2 扫描算子

1. CStoreScan算子

CStoreScan算子用于扫描基础表,按顺序扫描基础表,对应的代码源文件是“veccstore.cpp”;CStoreScan算子对应的主要数据结构是CStoreScanState,CStoreScanState继承于ScanState。具体定义代码如下:

typedef struct CStoreScanState : ScanState {
    Relation ss_currentDeltaRelation;
    Relation ss_partition_parent;
    TableScanDesc ss_currentDeltaScanDesc;
    bool ss_deltaScan;
    bool ss_deltaScanEnd;
    VectorBatch* m_pScanBatch;
    VectorBatch* m_pCurrentBatch;
    CStoreScanRunTimeKeyInfo* m_pScanRunTimeKeys;
    int m_ScanRunTimeKeysNum;
    bool m_ScanRunTimeKeysReady;
    CStore* m_CStore;
    CStoreScanKey csss_ScanKeys;
    int csss_NumScanKeys;
    bool m_fSimpleMap;
    bool m_fUseColumnRef;
    vecqual_func jitted_vecqual;
    bool m_isReplicaTable; /*复制表标记符*/
} CStoreScanState;

CStoreScan算子的相关函数包括:ExecInitCStoreScan(初始化节点)、ExecCStoreScan(执行节点)、ExecEndCStoreScan(退出节点)、ExecReScanCStoreScan(重置节点)。
ExecInitCStoreScan函数用于初始化CStoreScan算子。主要执行流程如下。
(1) 创建并初始化CStoreScan算子,为节点创建表达式上下文。
(2) 调用ExecAssignVectorForExprEval函数进行投影表达式的初始化。
(3) 调用ExecInitResultTupleSlot函数和ExecInitScanTupleSlot函数分别初始化用于投影结果和用于扫描的slot。
(4) 打开扫描表,调用ExecAssignResultTypeFromTL函数和ExecBuildVecProjectionInfo函数分别初始化结果扫描描述符和创建投影结构。
ExecCStoreScan函数是CStoreScan算子的主体函数,通过迭代的方式获取全部结果元组。
ExecEndCStoreScan函数用于在算子执行结束后清理CStoreScan算子。主要执行流程是:首先获取节点信息(包括Relation、ScanDesc),之后释放表达式上下文、元组,最后关闭相应的partition、relation。
ExecReScanCStoreScan函数用于重新执行扫描计划。主要执行流程是:首先重置runtime关键词,关闭当前节点partition信息,初始化接下来的partition信息,最后重置CStoreScan算子。

2. CStoreIndexScan算子

CStoreIndexScan算子用于使用索引对表进行扫描,如果过滤条件中涉及索引,可以使用该算子加速元组获取,对应的代码源文件是“veccstoreindexscan.cpp”。CStoreIndexScan算子对应的主要数据结构是CStoreIndexScanState,CStoreIndexScanState继承于CStoreScanState。具体定义代码如下:

typedef struct CStoreIndexScanState : CStoreScanState {
    CStoreScanState* m_indexScan;
    CBTreeScanState* m_btreeIndexScan;
    CBTreeOnlyScanState* m_btreeIndexOnlyScan;
    List* m_deltaQual;
    bool index_only_scan;
    /* 扫描索引并从基表中得到以下信息 */
    int* m_indexOutBaseTabAttr;
    int* m_idxInTargetList;
    int m_indexOutAttrNo;
    cstoreIndexScanFunc m_cstoreIndexScanFunc;
} CStoreIndexScanState;

CStoreIndexScan算子的相关函数包括:ExecInitCStoreIndexScan(初始化节点)、ExecCStoreIndexScanT(执行节点)、ExecEndCStoreIndexScan(退出节点)、ExecReScanCStoreIndexScan(重置节点)。
ExecInitCStoreIndexScan函数用于初始化CStoreIndexScan算子。主要执行流程是:首先创建CStoreScan执行节点scanstate,之后根据scanstate创建CStoreIndexScanState节点。最后打开相关的relation和index。
ExecCStoreIndexScanT函数是CStoreIndexScan算子的主体函数。主要执行流程是:首先会从计划节点中获取Btree的相关信息,设置runtime扫描关键词,最后循环地获取结果集,直到执行结束。
ExecEndCStoreIndexScan函数用于在执行结束时清理CStoreIndexScan算子。主要执行流程是:首先清理相应的CStoreScan算子,之后关闭相应的index、relation。
ExecReScanCStoreIndexScan函数用于重新执行扫描计划。主要执行流程是:首先重新扫描m_indexscan,之后重新扫描相关节点信息。

3. CStoreIndexHeapScan算子

CStoreIndexHeapScan算子用于对属性上的索引进行扫描,返回结果为一个位图,其中标记了满足条件的元组在页面中的偏移量,对应的代码源文件是veccstoreindexheapscan.cpp;CStoreIndexHeapScan算子对应的主要数据结构是CStoreIndexHeapScanState,继承于CStoreIndexScanState。其中包含的核心函数有:ExecCstoreInitIndexHeapScan(初始化节点)、ExecCstoreIndexHeapScan(执行节点)、ExecReScanCstoreIndexHeapScan(重置节点)、ExecEndCstoreIndexHeapScan(退出节点)。
ExecCstoreInitIndexHeapScan函数是用于初始化CStoreIndexHeapScan算子。主要执行流程是:首先将计划节点转换为执行节点CStoreScan,之后复制CStoreScan算子、计划节点信息完成CStoreIndexHeapScan算子的初始化。
ExecCstoreIndexHeapScan函数是CStoreIndexHeapScan算子的主体函数。主要执行流程是:首先更新timing标记,之后迭代地获取目标迭代批次,直到获取全部结果。
ExecReScanCstoreIndexHeapScan函数用于重新执行扫描计划,通过调用VecExecReScan函数、ExecReScanCStoreScan函数实现重新扫描。
ExecEndCstoreIndexHeapScan函数用于在执行结束后清理CStoreIndexHeapScan算子占用的资源,通过调用ExecEndNode函数和ExecEndCStoreScan函数清理计划节点和执行节点。

4. VecSubqueryScan算子

VecSubqueryScan算子将子计划作为扫描对象,实际执行中会转换为调用子节点计划,对应的代码源文件是vecsubqueryscan.cpp;VecSubqueryScan算子对应的主要数据结构是VecSubqueryScanState,继承于SubqueryScanState。包含的核心函数有:ExecInitVecSubqueryScan(初始化节点)、ExecVecSubqueryScan(执行节点)、ExecEndVecSubqueryScan(退出节点)、ExecReScanVecSubqueryScan(重置节点)。
ExecInitVecSubqueryScan函数是用于初始化VecSubqueryScan算子。主要执行流程是:首先初始化VecSubqueryScan执行算子,并为节点创建表达式上下文,接着初始化子查询计划,最后初始化元组和投影信息。
ExecVecSubqueryScan函数是VecSubqueryScan算子的主体函数。主要执行流程是:调用ExecVecScan执行算子,得到查询结果。
ExecReScanVecSubqueryScan函数用于重新执行扫描计划,通过调用ExecScanReScan函数重新扫描。
ExecEndVecSubqueryScan函数用于在执行结束后清理VecSubqueryScan算子占用的资源,通过调用ExecEndSubqueryScan函数进行清理。

5. VecForeignScan算子

VecForeignScan算子对应的代码源文件是“vecforeignscan.cpp”。VecForeignScan算子对应的主要数据结构是VecForeignScanState,继承于ForeignScanState。该算子包含的核心函数有:ExecInitVecForeignScan(初始化节点)、ExecVecForeignScan(执行节点)、ExecEndVecForeignScan(退出节点)、ExecReScanVecForeignScan(重置节点)。
ExecInitVecForeignScan函数是用于初始化VecForeignScan算子。主要执行流程如下。
(1) 创建VecForeignScanState执行节点。
(2) 设置表达式上下文。
(3) 调用ExecInitVecExpr函数依次为“ss.ps.targetlist”和“ss.ps.qual”初始化表达式。
(4) 调用ExecBuildVecProjectionInfo函数创建投影结构。
ExecVecForeignScan函数是VecForeignScan算子的主体函数,通过调用ExecVecScan执行算子,得到查询结果。
ExecReScanVecForeignScan函数用于重新执行扫描计划,通过调用ExecScanReScan函数实现重新扫描。
ExecEndVecForeignScan函数用于在执行结束后清理VecForeignScan算子占用的资源,通过调用MemoryContextDelete函数清除上下文和ExecEndForeignScan函数清除执行节点。

7.6.3 物化算子

1. VecMaterial算子

VecMaterial算子能够缓存需要多次重复扫描的子节点结果,有助于减少执行中的扫描代价。
VecMaterial算子对应的代码源文件是“vecmaterial.cpp”。VecMaterial算子对应的主要数据结构是VecMaterialState,继承于MaterialState。相关代码如下:

typedef struct VecMaterialState : public MaterialState {
    VectorBatch* m_pCurrentBatch;
    BatchStore* batchstorestate;
    bool from_memory;
} VecMaterialState;

VecMaterial算子的相关函数包括:ExecInitVecMaterial(初始化节点)、ExecVecMaterial(执行节点)、ExecEndVecMaterial(退出节点)、ExecReScanVecMaterial(重置节点)。
ExecInitVecMaterial函数用于初始化VecMaterial算子。主要执行流程如下。
(1) 创建并初始化VecMaterialState执行节点。
(2) 分别调用“ExecInitResultTupleSlot(estate, &matstate->ss.ps)”函数和“ExecInitScanTupleSlot(estate, &matstate->ss)”函数分配用于存储投影结果和用于扫描的slot。
(3) 调用“ExecAssignScanTypeFromOuterPlan(&matstate->ss)”函数初始化元组类型,调用“ExecAssignResultTypeFromTL(&matstate->ss.ps,matstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor->tdTableAmType)”函数初始化结果扫描描述符。
(4) 最后如果当前VecMaterial节点处于子计划中并且物化Stream数据,需要将其添加到estate->es_material_of_subplan中。
ExecVecMaterial函数是VecMaterial算子的主体函数,根据materalAll判断是否需要一次性物化所有元组,通过分别调用exec_vec_material_all函数、exec_vec_material_one函数完成算子的执行。其中exec_vec_material_all函数会一次性物化全部元组,之后根据需要返回部分元组,而exec_vec_material_one函数则会逐个物化元组。
ExecEndVecMaterial函数用于清理VecMaterial算子执行过程中使用的资源主要执行流程是:首先调用“ExecClearTuple(node->ss.ss_ScanTupleSlot)”函数清理tuple table,之后调用batchstore_end释放用于存储元组的资源,最后调用“ExecEndNode(outerPlanState(node)) ”函数清理子计划节点。
ExecReScanVecMaterial函数用于重新执行扫描计划,流程如图7-32所示。主要执行流程是:
(1) 根据eflags判断tuplestore是否进行其他操作(如REWIND、BACKWARD、RESTORE)。
(2) 若未进行其他操作,则直接调用“VecExecReScan(node->ss.ps.lefttree)”函数进行重新扫描,反之则需要进一步判断是否已执行了物化操作。
(3) 若未进行物化操作,则同样直接调用“VecExecReScan(node->ss.ps.lefttree)”函数进行重新扫描;若已产生物化结果,则需要根据进行操作的不同进行不同处理。
(4) 已产生物化结果可以分为2种情况:REWIND操作和其他操作。如果进行了REWIND操作,则需要调用batchstore_end函数释放已经存储的结果,之后调用“VecExecReScan(node->ss.ps.lefttree)”函数重新扫描。对于其他操作,需要判断当前计划是否为partition-wise join并且是否需要切换分区。
(5) 如是则同样需要调用batchstore_end和VecExecReScan函数重新扫描计划;反之只需要调用“batchstore_rescan(node->batchstorestate)”函数。
在这里插入图片描述

图7-32 ExecReScanVecMaterial函数执行流程

2. VecSort算子

VecSort算子用于缓存下层节点返回的所有结果元组并进行排序,对于结果元组较多时,会使用临时文件进行存储,并使用外排序进行排序操作。
VecSort算子对应的代码源文件是“vecsort.cpp”,VecSort算子对应的主要数据结构是VecSortState,继承于SortState。相应代码如下:
typedef struct VecSortState : public SortState {
VectorBatch* m_pCurrentBatch;
char* jitted_CompareMultiColumn;
char* jitted_CompareMultiColumn_TOPN;
} VecSortState;

VecSort算子的相应函数有:ExecInitVecSort(初始化节点)、ExecVecSort(执行节点)、ExecEndVecSort(退出节点)、ExecReScanVecSort(重置节点)。
ExecInitVecSort函数用于初始化VecSort算子。主要执行流程如下。
(1) 创建并初始化VecSortState执行节点。
(2) 分别调用“ExecInitResultTupleSlot(estate, &sort_state->ss.ps)”函数用于存储投影结果和“ExecInitScanTupleSlot(estate, &sort_state->ss)”函数用于初始化扫描元组槽。
(3) 调用“ExecAssignScanTypeFromOuterPlan(&sort_state->ss)”函数初始化元组类型,调用“ExecAssignResultTypeFromTL(&sort_stat->ss.ps,sort_stat->ss.ss_ScanTupleSlot->tts_tupleDescriptor->tdTableAmType)”函数初始化结果扫描描述符。
ExecVecSort函数是VecSort算子的主体函数。主要执行流程是:初次执行时,首先调用batchsort_begin_heap函数初始化元组缓存结构,之后循环执行从下层节点获取元组;调用sort_putbatch将获取的元组存放到缓存中;获取全部元组之后,调用batchsort_performsort进行排序;后续对VecSort算子的执行,调用batchsort_getbatch直接从缓存中获取一个元组。
ExecEndVecSort函数用于清理VecSort算子执行过程中使用的资源。主要执行流程是:首先调用ExecClearTuple函数依次清理“node->ss.ss_ScanTupleSlot”元组缓存和“node->ss.ps_ResultTupleSlot”排序后的元组缓存,之后调用batchsort_end函数释放用于元组排序的资源,最后调用“ExecEndNode(outerPlanState(node)) ”函数清理子计划节点。
ExecReScanVecSorts函数用于重新执行扫描计划,执行流程图如图33所示。主要执行流程是:
(1) 判断是否已经进行过排序;如没有执行过,则调用VecExecReScan函数重新扫描执行节点即可,反之则先判断子节点是否已经重新扫描。
(2) 如果子节点已经重新扫描,则调用ExecClearTuple函数清理已经排序的结果元组,调用batchsort_end函数清理“node->tuplesortstate”,调用VecExecReScan函数重新扫描执行节点;如果没有,则判断当前计划是否是“partition-wise join”并且需要切换分区。
(3)如果当前计划是“partition-wise join”并且需要切换分区,则同样需要调用batchstore_end函数和VecExecReScan函数进行重新扫描计划;否则只需要调用“batchstore_rescan(node->tuplesortstate)”。
ExecReScanVecSorts函数执行流程如图7-33所示。
在这里插入图片描述

图7-33 ExecReScanVecSort函数执行流程

3. VecLimit算子

VecLimit算子用于处理Limit子句,对应的代码源文件是“veclimit.cpp”。VecLimit算子对应的主要数据结构是VecLimitState,VecLimitState继承于LimitState。具体定义代码如下:

struct VecLimitState : public LimitState {
    VectorBatch* subBatch;
};

VecLimit算子的相关函数包括ExecInitVecLimit(初始化节点)、ExecVecLimit(执行节点)、ExecReScanVecLimit(重置节点)、ExecEndVecLimit(退出节点)。
ExecInitVecLimit函数用于初始化VecLimit算子,将VecLimit计划节点转换为VecLimit执行节点。主要执行流程如下。
(1) 创建VecLimit执行节点,创建表达式上下文,分别初始化limitOffset(调用“ExecInitExpr((Expr)node->limitOffset, (PlanState)limit_state))” 函数和limitCount(调用“ExecInitExpr((Expr)node->limitCount, (PlanState)limit_state)”函数)表达式。
(2) 调用“ExecInitResultTupleSlot(estate, &limit_state->ps) ”函数进行初始化元组。
(3) 调用“ExecInitNode(outer_plan, estate, eflags) ”函数进行初始化外部计划。最后置空投影结构。
ExecVecLimit函数是VecLimit算子的主体函数。函数中通过switch来处理VecLimit算子中存在的多种状态,node->Istate存在的状态有LIMIT_INITIAL、LIMIT_RESCAN、LIMIT_EMPTY、LIMIT_INWINDOW、LIMIT_SUBPLANEOF、LIMIT_WINDOWEND、LIMIT_WINDOWSTART。其中LIMIT_INITIAL表示处理Limit算子初始化,LIMIT_RESCAN表示重新执行子节点计划,LIMIT_EMPTY表示Limit算子是空集,LIMIT_INWINDOW表示处理窗口函数(在窗口函数内前向和后向移动),LIMIT_SUBPLANEOF表示处理子节点计划(移动到子节点计划尾部),LIMIT_WINDOWEND表示在窗口结尾部分结束,LIMIT_WINDOWSTART表示在窗口开始部分结束。
ExecEndVecLimit函数用于在执行VecLimit算子结束时释放相关资源,通过依次调用“ExecFreeExprContext(&node->ps)”函数和“ExecEndNode(outerPlanState(node))”函数释放表达式上下文和节点相关信息。
ExecReScanVecLimit函数用于重新执行扫描计划。在参数发生改变时,通过调用“recompute_limits(node)”函数完成执行节点的重新扫描和VecLimit状态机的重置。在chgParam为空时,还需要调用VecExecReScan函数重新扫描计划节点。

4. VecGroup算子

VecGroup算子用于处理SQL语句中的“GROUP BY”子句,对满足条件的元组做分组处理。
VecGroup算子对应的代码源文件是“vecgroup.cpp”。VecGroup算子对应的主要数据结构是VecGroupState,继承于GroupState。相关代码如下:

struct VecGroupState : public GroupState {
    void** container;
    void* cap;
    uint16 idx;
    int cellSize;
    bool keySimple;
    FmgrInfo* buildFunc;
    FmgrInfo* buildScanFunc;
    VectorBatch* scanBatch;
    VarBuf* currentBuf;
    VarBuf* bckBuf;
    vecqual_func jitted_vecqual; 
};

VecGroup算子的相应函数有:ExecInitVecGroup(初始化节点)、ExecVecGroup(执行节点)、ExecEndVecGroup(退出节点)、ExecReScanVecGroup(重置节点)。
ExecInitVecGroup函数用于初始化VecGroup算子,主要执行流程如下。
(1) 创建并初始化VecGroupState执行节点,并为节点创建表达式上下文。
(2) 调用“ExecInitResultTupleSlot(estate,&grp_state->ss.ps);”函数分配存储投影结果的slot。
(3) 调用投影表达式初始化函数ExecInitVecExpr依次对plan.targetlist和plan.qual进行初始化。
(4) 调用ExecInitNode函数初始化子节点。
(5) 调用ExecAssignResultTypeFromTL函数初始化结果扫描描述符和调用ExecAssignVectorForExprEval函数创建投影结构。
ExecVecGroup函数是VecGroup算子的主体函数。主要执行流程如下。
(1) 获取下层元组中符合having子句条件的第1个元组。
(2) 依次获取组内的所有元组,直到获取到分组属性不同的元组,此时表示当前分组获取结束;如果获取到空元组,则表示完成分组操作,设置grp_done字段为true并结束执行。
(3) 扫描下一个符合having条件的元组,将缓存的元组作为分组的开始,并返回新元组。
(4) 重复(2)、(3)直到结束。
ExecEndVecGroup函数用于清理VecGroup算子执行过程中使用的资源。主要执行流程是:首先调用ExecFreeExprContext函数清理表达式上下文,最后调用“ExecEndNode(outerPlanState(node))”函数清理子计划节点。
ExecReScanVecGroup函数用于重新执行扫描计划,通过调用VecExecReScan函数实现重新扫描。

5. VecAggregation算子

VecAggregation算子用于处理含有聚集函数的操作,将同一分组下的多个元组合并成一个聚集结果元组。
VecAggregation算子对应的代码源文件是“vecagg.cpp”。VecAggregation算子对应的主要数据结构是VecAggState,继承于AggState。相应代码如下:

typedef struct VecAggState : public AggState {
    void* aggRun;
    VecAggInfo* aggInfo;
    char* jitted_hashing;
    char* jitted_sglhashing;
    char* jitted_batchagg;
    char* jitted_sonicbatchagg;
    char* jitted_SortAggMatchKey;
} VecAggState;

VecAggregation算子对应的核心函数有:ExecInitVecAggregation(初始化节点)、ExecVecAggregation(执行节点)、ExecEndVecAggregation(退出节点)、ExecReScanVecAggregation(重置节点)。
ExecInitVecAggregation函数用于初始化VecAggregation算子。主要执行流程如下。
(1) 创建并初始化VecAggState执行节点,并调用ExecAssignExprContext函数为节点创建表达式上下文。
(2) 调用ExecInitScanTupleSlot函数分配用于扫描的slot,调用ExecInitResultTupleSlot函数分配存储投影结果的slot,调用ExecInitExtraTupleSlot函数为sort_slot进行初始化。
(3) 调用投影表达式初始化函数ExecInitVecExpr依次对“plan.targetlist”和“plan.qual”进行初始化。
(4) 调用ExecInitNode函数初始化子节点,获取其中的Aggref节点。
(5) 使用每个Aggref节点中包含的聚集函数信息进行初始化,构造出对应的AggStatePerAgg。
(6) 最后根据策略类型,初始化相应的状态信息。
ExecVecAggregation函数是VecAggregation算子的主体函数。根据策略类型的不同(hash、plain、sort),调用不同的Runner函数执行。
ExecEndVecAggregation函数用于清理VecAggregation算子执行过程中使用的资源。主要执行流程是:依据选择的策略Hash、sort、plain分别调用freeMemoryContext函数、endSortAgg函数、endPlainAgg函数清理节点信息,之后分别调用ExecFreeExprContext函数和ExecClearTuple函数对表达式上下文和元组缓存进行清理。
ExecReScanVecAggregation函数用于重新执行扫描计划。主要执行流程是:根据策略类型分别调用相应的ResetNecessary函数重置相应执行节点,最后调用VecExecReScan函数实现重新扫描。

6. VecWindowAgg算子

VecWindowAgg算子用于处理窗口函数的聚集操作。不同于Agg算子,窗口函数不会将同一分组中的元组合并为一个,这样就需要对每个元组都产生一个结果元组,其中包含对应的聚集计算结果。
VecWindowAgg算子对应的代码源文件是“vecwindowagg.cpp”。VecWindowAgg算子对应的主要数据结构是VecWindowAggState,继承于WindowAggState。相关代码如下:

typedef struct VecWindowAggState : public WindowAggState {
    void* VecWinAggRuntime;
    VecAggInfo* windowAggInfo;
} VecWindowAggState;

VecWindowAgg算子中对应的核心函数有:ExecInitVecWindowAgg(初始化节点)、ExecVecWindowAgg(执行节点)、ExecEndVecWindowAgg(退出节点)、ExecReScanVecWindowAgg(重置节点)。
ExecInitVecWindowAgg函数用于初始化VecWindowAgg算子,主要执行流程如下。
(1) 创建并初始化VecWindowAgg执行节点,并调用ExecAssignExprContext函数为节点创建表达式上下文。
(2) 调用ExecInitResultTupleSlot函数分配存储投影结果的slot,调用ExecInitScanTupleSlot函数分配用于扫描的slot。
(3) 调用ExecInitVecExpr函数为ps.targetlist初始化投影表达式。
(4) 初始化分区判断函数和排序属性是否相同的操作函数,保存在partEqfunctions、ordEqfunctions中。
(5) 初始化funcs指向的表达式树,构造相关调用信息并存放在perfunc中。
ExecVecWindowAgg函数是VecWindowAgg算子的主体函数,通过调用getBatch执行算子,得到窗口函数的投影结果。
ExecEndVecWindowAgg函数用于清理VecWindowAgg算子执行过程中使用的资源,通过调用batchstore_end函数清理元组缓存,通过调用ExecEndNode函数清理执行节点。
ExecReScanVecWindowAgg函数用于重新执行扫描计划,通过调用ResetNecessary函数重置相应执行节点,通过调用VecExecReScan函数实现重新扫描。

7. VecSetOp算子

VecSetOp算子用于处理EXECEPT和INTERSECT集合操作。一般一个VecSetOp算子中只能处理两个集合之间的集合操作,对于多个集合之间的集合操作,需要多个SetOp实现。
VecSetOp算子对应的代码源文件是“vecsetop.cpp”。VecSetOp算子对应的主要数据结构是VecSetOpState,继承于SetOpState。相关代码如下:

typedef struct VecSetOpState : public SetOpState {
    void* vecSetOpInfo;
} VecSetOpState;

VecSetOp算子中对应的核心函数有:ExecInitVecSetOp(初始化节点)、ExecVecSetOp(执行节点)、ExecEndVecSetOp(退出节点)、ExecReScanVecSetOp(重置节点)。
ExecInitVecSetOp函数用于初始化VecSetOp算子。主要执行流程如下。
(1) 创建并初始化VecSetOpState执行节点。
(2) 调用ExecInitResultTupleSlot函数分配存储投影结果的slot。
(3) 调用ExecInitnode函数初始化子节点。
(4) 调用ExecAssignResultTypeFromTL函数初始化结果扫描描述符。
ExecVecSetOp函数是VecSetOp算子的主体函数,通过执行VecSetOp算子状态机,产生resultBatch。
ExecEndVecSetOp函数用于清理VecSetOp算子执行过程中使用的资源。通过调用freeMemoryContext函数释放内存上下文,通过调用ExecClearTuple函数清理元组缓存,通过调用ExecEndNode函数清理执行节点。
ExecReScanVecSetOp函数用于重新执行扫描计划,通过调用ExecClearTuple函数清理元组结果缓存,通过调用ResetNecessary函数重置相应执行节点。

7.6.4 连接算子

1. VecNestLoop算子

VecNestLoop算子对应的主要数据结构是VecNestLoopState,VecNestLoopState继承于NestLoopState。具体定义代码如下:

struct VecNestLoopState : public NestLoopState {
    void* vecNestLoopRuntime;
    vecqual_func jitted_vecqual;
    vecqual_func jitted_joinqual;
};

VecNestLoop算子的相关函数包括:ExecInitVecNestLoop(初始化节点)、ExecVecNestLoop(执行节点)、ExecEndVecNestLoop(退出节点)、ExecReScanVecNestLoop(重置节点)。
ExecInitVecNestLoop函数用于初始化VecNestLoop执行算子。主要执行流程如下。
(1) 初始化VecNestLoop执行算子。
(2) 为节点创建表达式上下文,分别处理左右子树,得到外执行计划节点和内执行计划节点。
(3) 初始化元组和投影信息。
ExecVecNestLoop函数是执行VecNestLoop的主体函数,通过执行VecNestLoop状态机,并获得结果元组。
ExecEndVecNestLoop函数用于在执行结束时清理VecNestLoop算子。主要执行流程是:首先释放表达式上下文,之后清空元组,最后清空子计划节点。
ExecReScanVecNestLoop函数用于重新执行扫描计划。主要执行流程是:首先把VecNestLoop计划节点转换成外计划执行节点,之后判断外计划执行节点的chgParam是否为空,若chgParam为空,则重新扫描节点。

2. VecMergeJoin算子

VecMergeJoin算子对应的主要数据结构是VecMergeJoinState,VecMergeJoinState继承于MergeJoinState。具体定义代码如下:

struct VecMergeJoinState : public MergeJoinShared {
    /* 向量化执行支持 */
    VecMergeJoinClause mj_Clauses;
    MJBatchOffset mj_OuterOffset;
    MJBatchOffset mj_InnerOffset;
    ExprContext* mj_OuterEContext;
    ExprContext* mj_InnerEContext;
    MJBatchOffset mj_MarkedOffset;
    VectorBatch* mj_MarkedBatch;
    BatchAccessor m_inputs[2];
    MJBatchOffset m_prevInnerOffset;
    bool m_prevInnerQualified;
    MJBatchOffset m_prevOuterOffset;
    bool m_prevOuterQualified;
    bool m_fDone;
    VectorBatch* m_pInnerMatch;
    MJBatchOffset* m_pInnerOffset;
    VectorBatch* m_pOuterMatch;
    MJBatchOffset* m_pOuterOffset;
    VectorBatch* m_pCurrentBatch;
    VectorBatch* m_pReturnBatch;
vecqual_func jitted_joinqual; 
};

VecMergeJoin算子的相关函数包括:ExecInitVecMergeJoin(初始化节点)、ExecVecMergeJoinT(执行节点)、ExecEndVecMergeJoin(退出节点)、ExecReScanVecMergeJoin(重置节点)。
ExecInitVecMergeJoin函数用于初始化VecMergeJoin执行算子。主要执行流程如下。
(1) 初始化VecMergeJoin执行算子。
(2) 为节点创建表达式上下文,分别处理左右子树,得到外执行计划节点和内执行计划节点。
(3) 初始化元组和投影信息。
ExecVecMergeJoinT函数是执行VecMergeJoin的主体函数,执行VecMergeJoin状态机,并根据join类型,获取结果元组。
ExecEndVecMergeJoin函数用于在执行结束时清理VecMergeJoin算子。首先释放表达式上下文,之后清空元组,最后清空左右子树节点。
ExecReScanVecMergeJoin函数用于重新执行扫描计划。主要执行流程是:首先重置节点相关参数,之后判断左右子树的chgParam是否为空;若chgParam为空时,则重新扫描节点。

3. VecHashJoin算子

VecHashJoin算子对应的主要数据结构是VecHashJoinState,VecHashJoinState继承于HashJoinState。具体定义如下:

typedef struct VecHashJoinState : public HashJoinState {
    int joinState;
    void* hashTbl;
    FmgrInfo* eqfunctions;
vecqual_func jitted_joinqual;
vecqual_func jitted_hashclause; 
    char* jitted_innerjoin;
    char* jitted_matchkey;
    char* jitted_buildHashTable;
    char* jitted_probeHashTable;
    int enable_fast_keyMatch;
    BloomFilterRuntime bf_runtime;
    char* jitted_hashjoin_bfaddLong;
    char* jitted_hashjoin_bfincLong;
    char* jitted_buildHashTable_NeedCopy;
} VecHashJoinState;

VecHashJoin算子的相关函数包括:ExecInitVecHashJoin(初始化节点)、ExecVecHashJoin(执行节点)、ExecEndVecHashJoin(退出节点)、ExecReScanVecHashJoin(重置节点)。
ExecInitVecHashJoin函数用于初始化Vechash join执行算子,并把VecHashJoin计划节点转换成计划执行节点。主要执行流程是:首先处理左子树,得到外执行计划节点;再处理右子树,得到内执行计划节点;最后初始化元组和投影信息。
ExecVecHashJoin函数是执行VecHashJoin的主体函数,执行VecHashJoin状态机。
ExecEndVecHashJoin函数用于在执行结束时清理VecHashJoin算子。主要执行流程是:首先释放内存上下文,之后释放表达式,清空左右子树。流程如图7-34所示。
在这里插入图片描述

图7-34 ExecEndVecHashJoin函数执行流程

ExecReScanVecHashJoin函数用于重新执行扫描计划。主要执行流程是:首先判断状态信息,如哈希表为空时,只需要重新扫描左子树计划,否则需要重新构建哈希表。

7.7 小结

本章节主要介绍了执行器的总体框架、执行器算子、向量化引擎;向量化引擎通过编译执行模块实现执行加速。执行器接收Plan(优化器输出),对Plan做转换处理,生成状态树,状态树的节点对应执行算子(这些算子利用存储和索引提供的接口,实现数据读写);执行器是SQL语句同存储交互的中介。这些执行算子有统一的接口以及相似的执行流程(初始化、迭代执行、清理3个过程)。向量化引擎面向OLAP场景需求,同编译执行相结合提供高效执行效率。

感谢大家学习第七章执行器解析中“7.6 向量化引擎”及“7.7 小结”的精彩内容,下一篇我们开启“8.1 概述”、“8.2 自调优”的相关内容的介绍。
敬请期待。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK