【OpenGauss源码学习 —— (VecGroup)】

声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 OpenGauss5.1.0 的开源代码和《OpenGauss数据库源码解析》一书

ExecInitVecGroup 函数

  ExecInitVecGroup 函数是一个用于初始化向量化分组操作节点的函数。在执行查询计划的过程中,该函数负责创建和配置 VecGroupState 结构设置向量化执行的相关属性,并初始化表达式上下文和元组槽。它还处理子节点的初始化目标列表和资格表达式的向量化,以及准备相等比较函数。通过这些步骤,该函数为后续的分组聚合操作建立了基础,确保能够高效地处理数据分组和聚合的计算。整体上,这个函数是向量化分组操作执行的准备阶段,确保在执行过程中能够有效管理状态和资源。函数源码如下所示:(路径:src\gausskernel\runtime\vecexecutor\vecnode\vecgroup.cpp

VecGroupState* ExecInitVecGroup(VecGroup* node, EState* estate, int eflags)
{
    VecGroupState* grp_state = NULL; // 声明一个指向 VecGroupState 的指针,用于保存聚合状态
    ScalarDesc unknown_desc; // 未使用的标量描述符

    // 检查不支持的标志
    Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));

    // 创建状态结构
    grp_state = makeNode(VecGroupState); // 创建 VecGroupState 节点
    grp_state->ss.ps.vectorized = true; // 设置为向量化执行
    grp_state->ss.ps.plan = (Plan*)node; // 将当前节点赋值给计划属性
    grp_state->ss.ps.state = estate; // 将执行状态赋值
    grp_state->grp_done = false; // 初始化完成状态为 false

    // 创建表达式上下文
    ExecAssignExprContext(estate, &grp_state->ss.ps); // 分配表达式上下文

    // 初始化元组表
    ExecInitResultTupleSlot(estate, &grp_state->ss.ps); // 初始化结果元组槽

    // 初始化子表达式
    grp_state->ss.ps.targetlist = (List*)ExecInitVecExpr((Expr*)node->plan.targetlist, (PlanState*)grp_state); // 初始化目标列表
    grp_state->ss.ps.qual = (List*)ExecInitVecExpr((Expr*)node->plan.qual, (PlanState*)grp_state); // 初始化资格表达式

#ifdef ENABLE_LLVM_COMPILE
    /*
     * 检查 nlstate->js.joinqual 和 nlstate->js.ps.qual 表达式列表是否可以代码生成。
     */
    llvm::Function* grp_vecqual = NULL; // 声明一个 LLVM 函数指针
    dorado::GsCodeGen* llvm_code_gen = (dorado::GsCodeGen*)t_thrd.codegen_cxt.thr_codegen_obj; // 获取 LLVM 代码生成器对象
    bool consider_codegen =
        CodeGenThreadObjectReady() && // 确保代码生成线程对象准备好
        CodeGenPassThreshold(((Plan*)node)->plan_rows, estate->es_plannedstmt->num_nodes, ((Plan*)node)->dop); // 检查代码生成阈值
    if (consider_codegen) {
        grp_vecqual = dorado::VecExprCodeGen::QualCodeGen((List*)grp_state->ss.ps.qual, (PlanState*)grp_state); // 生成资格代码
        if (grp_vecqual != NULL)
            llvm_code_gen->addFunctionToMCJit(grp_vecqual, reinterpret_cast<void**>(&(grp_state->jitted_vecqual))); // 添加到 JIT 编译器
    }
#endif

    // 初始化子节点
    outerPlanState(grp_state) = ExecInitNode(outerPlan(node), estate, eflags); // 初始化外部计划

    /*
     * 初始化结果元组类型和投影信息。
     * 分组节点的结果元组槽总是保存虚拟元组,因此默认表访问方法类型设置为 HEAP。
     */
    ExecAssignResultTypeFromTL(&grp_state->ss.ps); // 从目标列表中分配结果类型

    // 为表达式评估分配向量
    grp_state->ss.ps.ps_ProjInfo = ExecBuildVecProjectionInfo(grp_state->ss.ps.targetlist,
        node->plan.qual,
        grp_state->ss.ps.ps_ExprContext,
        grp_state->ss.ps.ps_ResultTupleSlot,
        NULL); // 构建投影信息

    ExecAssignVectorForExprEval(grp_state->ss.ps.ps_ExprContext); // 为表达式评估分配向量

    // 预计算用于内循环的 fmgr 查找数据
    grp_state->eqfunctions = execTuplesMatchPrepare(node->numCols, node->grpOperators); // 准备相等比较函数

    // 初始化向量执行过程中的变量并绑定 FmgrInfo 中的函数
    grp_state->cap = (void*)palloc(sizeof(Encap)); // 分配内存用于封装
    InitGrpUniq<VecGroupState>(grp_state, node->numCols, node->grpColIdx); // 初始化唯一分组状态
    return grp_state; // 返回聚合状态
}

ExecVecGroup 函数

  ExecVecGroup 函数用于执行向量化的分组操作。该函数首先检查分组是否已经完成,然后通过循环不断从外部计划获取数据批次,并将其存储在分组容器中。每次获取批次后,它会调用相应的函数将数据填充到容器中。当容器满时,会将其内容转储到扫描批次中,并通过调整索引来管理剩余数据。最后,当没有更多数据可处理时,函数会处理未满的容器内容,并生成最终的结果批次。总体而言,该函数实现了高效的批处理分组功能,确保在向量化环境中可以快速和高效地处理数据分组和聚合的计算。函数源码如下所示:(路径:src\gausskernel\runtime\vecexecutor\vecnode\vecgroup.cpp

VectorBatch* ExecVecGroup(VecGroupState* node)
{
    void** grp_container = node->container; // 获取分组容器,用于存储当前组的信息
    uint16 i = 0; // 初始化循环索引
    uint16* idx = &(node->idx); // 获取当前索引,表示分组容器中的有效元素个数
    VectorBatch* res_batch = NULL; // 初始化结果批次
    Encap* cap = (Encap*)node->cap; // 获取封装对象,用于传递批次和函数参数
    cap->eqfunctions = node->eqfunctions; // 设置相等比较函数
    errno_t rc; // 错误码

    if (node->grp_done) // 如果分组操作已经完成,则返回 NULL
        return NULL;

    node->bckBuf->Reset(); // 重置备份缓冲区
    node->scanBatch->Reset(true); // 重置扫描批次

    for (;;) { // 开始一个无限循环
        // 获取下一个批次,如果为 NULL 则跳出循环
        VectorBatch* batch = VectorEngine(outerPlanState(node)); // 从外部计划中获取向量批次
        if (unlikely(BatchIsNull(batch))) // 检查批次是否为空
            break; // 如果为空,退出循环

        // 调用 buildFunc 函数,将批次读入 grp_container,使用 idx 作为当前插入位置
        cap->batch = batch; // 将当前批次赋值给封装对象
        (void)FunctionCall2(node->buildFunc, PointerGetDatum(node), PointerGetDatum(cap)); // 调用函数将数据填充到 grp_container

        // 当 grp_container 满时,调用 buildScanFunc 将 BatchMaxSize 个单元从 grp_container 转储到 scanBatch
        // 用于生成外部批次,然后调整 idx,从剩余单元开始重新计数,并重置尾部单元
        if (*idx >= BatchMaxSize) { // 如果索引超过最大批次大小
            cap->batch = node->scanBatch; // 将当前批次指向扫描批次
            for (i = 0; i < BatchMaxSize; i++) { // 循环处理每个单元
                (void)FunctionCall2(node->buildScanFunc, PointerGetDatum(grp_container[i]), PointerGetDatum(cap)); // 调用函数转储数据
            }
            uint16 remain_grp = *idx - BatchMaxSize + 1; // 计算剩余的组
            for (i = 0; i < remain_grp; i++) { // 循环调整 grp_container
                grp_container[i] = grp_container[BatchMaxSize + i]; // 将剩余单元移至前面
            }
            // 将 grp_container 的尾部重置为 NULL
            rc = memset_s(&grp_container[remain_grp],
                sizeof(GUCell*) * (2 * BatchMaxSize - remain_grp),
                0,
                sizeof(GUCell*) * (2 * BatchMaxSize - remain_grp)); // 清零尾部
            securec_check(rc, "\0", "\0"); // 检查安全性
            *idx = remain_grp - 1; // 更新索引
            res_batch = ProduceBatch(node); // 生成结果批次
            if (unlikely(BatchIsNull(res_batch))) { // 如果结果批次为空
                // 释放空间
                node->bckBuf->Reset(); // 重置备份缓冲区
                // 重置批次
                node->scanBatch->Reset(true); // 重置扫描批次
                continue; // 继续循环
            } else
                return res_batch; // 返回结果批次
        }
    }

    // 如果没有数据
    if (0 == *idx && NULL == grp_container[0]) { // 如果索引为 0 且容器第一个元素为 NULL
        return NULL; // 返回 NULL
    }

    // 处理剩余的 grp_container 数据
    cap->batch = node->scanBatch; // 将批次指向扫描批次
    for (i = 0; i <= *idx; i++) { // 遍历 grp_container
        (void)FunctionCall2(node->buildScanFunc, PointerGetDatum(grp_container[i]), PointerGetDatum(cap)); // 调用函数处理每个单元
    }

    node->grp_done = true; // 设置分组完成状态为 true
    return ProduceBatch(node); // 返回最终结果批次
}

ProduceBatch 函数

  ProduceBatch 函数的主要作用是生成一个包含符合条件的结果的向量批次。函数首先检查当前的扫描批次是否为空,如果为空,则返回 NULL。接着,它重置表达式上下文并设置相关的批次信息。然后,如果存在质检条件qual),函数会检查是否可以使用 LLVM 优化来生成代码。如果可以,它将调用 JIT 编译的函数来获取符合条件的标量向量;如果不可以,则使用普通的执行函数进行处理。如果获取的标量向量为空,函数将返回 NULL。最后,函数应用选择器并调用投影函数生成最终的结果批次。总体来说,ProduceBatch 函数通过结合选择和投影逻辑,实现了从数据批次中提取符合条件的数据的功能。函数源码如下所示:(路径:src\gausskernel\runtime\vecexecutor\vecnode\vecgroup.cpp

static VectorBatch* ProduceBatch(VecGroupState* node)
{
    ExprContext* expr_context = NULL; // 声明表达式上下文指针
    VectorBatch* p_res = NULL; // 声明结果批次指针
    VectorBatch* batch = node->scanBatch; // 获取当前的扫描批次

    // 检查是否有输入行,如果没有则返回 NULL
    if (batch == NULL)
        return NULL; // 如果批次为空,直接返回 NULL

    expr_context = node->ss.ps.ps_ExprContext; // 获取当前节点的表达式上下文
    ResetExprContext(expr_context); // 重置表达式上下文,以便重新使用
    expr_context->ecxt_scanbatch = batch; // 设置扫描批次
    expr_context->ecxt_outerbatch = batch; // 设置外部批次为当前扫描批次

    // 如果有质检条件
    if (list_length((List*)node->ss.ps.qual) != 0) {
        ScalarVector* p_vector = NULL; // 声明标量向量指针

        /*
         * 如果 grp_state->node->ss.ps.qual 可以使用 LLVM 优化生成代码,则先使用 LLVM 优化
         * (成本模型已经考虑过)
         */
        if (node->jitted_vecqual) {
            p_vector = node->jitted_vecqual(expr_context); // 使用 JIT 编译的函数获取标量向量

            if (HAS_INSTR(&node->ss, false)) { // 检查是否需要记录指标
                node->ss.ps.instrument->isLlvmOpt = true; // 标记为使用 LLVM 优化
            }
        } else
            p_vector = ExecVecQual(node->ss.ps.qual, expr_context, false); // 如果没有 JIT 编译,则使用普通执行函数

        if (p_vector == NULL) // 如果标量向量为空
            return NULL; // 返回 NULL,表示没有满足条件的行
        batch->Pack(expr_context->ecxt_scanbatch->m_sel); // 将扫描批次的选择器应用于当前批次
    }

    // 设置表达式上下文中的选择使用标志
    expr_context->m_fUseSelection = node->ss.ps.ps_ExprContext->m_fUseSelection;
    p_res = ExecVecProject(node->ss.ps.ps_ProjInfo); // 执行向量化投影,生成结果批次
    return p_res; // 返回结果批次
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值