在上一篇openGauss数据库源码解析系列文章——执行器解析(二)中,介绍了执行器解析中“7.4 表达式计算”及“7.5 编译执行”的相关内容,本篇将介绍“7.6 向量化引擎”的精彩内容。完整版内容请查看CSDN·Gauss松鼠会专栏博客,以下内容为章节试读:
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执行算子。执行流程如图一所示,主要执行流程如下。
(1) 创建并初始化VecResult执行节点,并为节点创建表达式上下文。
(2) 调用“ExecInitResultTupleSlot(estate, &res_state->ps)”函数分配存储投影结果的slot。
(3) 调用投影表达式初始化函数ExecInitVecExpr依次对ps.targetlist、ps.qual和resconstantqual进行初始化。
(4) 分别调用ExecAssignResultTypeFromTL函数和ExecAssignVectorForExprEval函数进行扫描描述符的初始化和投影结构的创建。
图一 ExecInitVecResult函数执行流程
ExecVecResult函数是执行VecResult的主体函数。执行流程如图二所示,主要执行流程如下。
(1) 检查是否需要计算常量表达式。
(2) 若需要则重新计算表达式,设置检查标识(如果常量计算表达式结果为false时,则设置约束检查标识位)。
(3) 获取结果元组。
图二 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算子。执行执行流程如图三所示,主要执行流程如下。
(1) 创建并初始化执行节点VecAppend。
(2) 分配存储投影结果的slot。
(3) 循环初始化子计划链表。
(4) 初始化扫描描述符并设置初始迭代。
图三 ExecInitVecAppend函数执行流程
ExecVecAppend函数是执行VecAppend算子的主体函数。执行流程如图四所示,每次从子计划中获取一条元组,当取回全部元组时,移动到下一个子计划,直到执行全部子计划。
图四 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函数分别初始化结果扫描描述符和创建投影结构。
……(本节内容未完)