当前位置: 首页 > news >正文

【PostgreSQL内核学习:通过 ExprState 提升哈希聚合与子计划执行效率(二)】

PostgreSQL内核学习:通过 ExprState 提升哈希聚合与子计划执行效率(二)

  • 引言
  • ExecBuildHash32FromAttrs
    • 示例 SQL 查询
    • 函数运行流程与代码解释
    • 最终 ExprState 结构
    • 执行示例
    • 总结
  • ExecComputeSlotInfo
    • 示例 SQL 查询
    • 函数注释与解释
      • 作用
      • 参数
      • 返回值
      • 执行流程(结合示例)
    • 与 `ExecBuildHash32FromAttrs` 的关系
    • 功能结构流程图
      • 流程图说明
    • 示例执行总结
    • 总结
  • ExprEvalPushStep
    • 示例 SQL 查询(上下文)
    • 函数注释与解释
      • 作用
      • 参数
      • 返回值
      • 执行流程(结合示例)
      • 示例调用
    • 功能结构流程图(补充)
      • 流程图说明
    • 示例执行总结
    • 总结

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

引言

  为了更清晰地理解 ExecBuildHash32FromAttrs 函数的运行流程,我将通过一个详细的 SQL 查询示例,结合每行代码的执行,逐步说明该函数如何构建哈希计算的 ExprState
  ExecBuildHash32FromAttrsPostgreSQL 中用于为指定列构建哈希计算执行计划的核心函数,常用于 GROUP BYNOT IN 子查询的哈希操作,特别是在优化补丁(“Use ExprStates for hashing in GROUP BY and SubPlans”)中。本例将使用一个具体的 SQL 查询,覆盖每行代码的解释,并以清晰的方式展示函数的逻辑流程。
  其他详细描述可见:【PostgreSQL内核学习:通过 ExprState 提升哈希聚合与子计划执行效率(一)】


ExecBuildHash32FromAttrs

示例 SQL 查询

假设有以下 SQL 查询:

SELECT region, department, SUM(amount)
FROM sales
GROUP BY region, department;
  • 表结构

    • sales 包含列:region (VARCHAR), department (VARCHAR), amount (INTEGER)。
    • 示例数据:
      region  | department | amount
      --------+------------+-------
      North   | Sales      | 100
      South   | Marketing  | 200
      North   | Marketing  | 150
      
  • 查询目标:按 regiondepartment 分组,计算 amount 的总和。

  • 哈希计算:需要为 regiondepartment 列生成组合哈希值,用于哈希表分组。

  在执行计划中,PostgreSQL 会调用 ExecBuildHash32FromAttrsGROUP BY 的列(regiondepartment)构建哈希计算的 ExprState,以高效计算哈希值并支持 JIT 编译。


函数运行流程与代码解释

  以下是 ExecBuildHash32FromAttrs 的代码,结合示例查询,逐行解释其执行过程,展示如何为 regiondepartment 构建哈希计算的执行计划。

/* * 构建一个 ExprState,用于对指定的列(attnums,由 keyColIdx 提供)调用哈希函数。* 当 numCols > 1 时,将每个哈希函数返回的哈希值组合成一个单一的哈希值。** desc: 要哈希的列的元组描述符* ops: 用于元组描述符的 TupleTableSlotOps 操作* hashfunctions: 每个列的哈希函数(FmgrInfo),数量与 numCols 对应,需保持分配状态* collations: 调用哈希函数时使用的排序规则* numCols: hashfunctions、collations 和 keyColIdx 的数组长度* parent: 评估 ExprState 的 PlanState 节点* init_value: 初始哈希值,通常为 0,非零值会略微降低性能,仅在必要时使用*/
ExprState *
ExecBuildHash32FromAttrs(TupleDesc desc, const TupleTableSlotOps *ops,FmgrInfo *hashfunctions, Oid *collations,int numCols, AttrNumber *keyColIdx,PlanState *parent, uint32 init_value)
{// 创建一个新的 ExprState 节点,用于存储哈希计算的执行计划ExprState  *state = makeNode(ExprState);// 示例:分配一个新的 ExprState 节点,初始化为空,准备存储步骤序列// 结果:state->steps 为空,state->parent = NULL// 初始化一个临时的 ExprEvalStep 结构,用于构建执行步骤ExprEvalStep scratch = {0};// 示例:创建一个空的 ExprEvalStep,字段如 opcode、resvalue 等初始化为 0 或 NULL// 结果:scratch 用于临时配置每个步骤,随后推入 state->steps// 初始化中间结果存储,用于存储多列哈希计算的中间值NullableDatum *iresult = NULL;// 示例:iresult 初始化为 NULL,后续可能分配内存存储中间哈希值// 结果:iresult = NULL// 定义操作码,用于指定当前步骤的类型(如提取列值或调用哈希函数)intptr_t	opcode;// 示例:opcode 用于存储当前步骤的操作类型(如 EEOP_INNER_FETCHSOME)// 结果:opcode 未初始化// 记录最大列编号,用于确定需要解构的元组范围AttrNumber	last_attnum = 0;// 示例:last_attnum 初始化为 0,用于跟踪最大列编号// 结果:last_attnum = 0// 断言列数非负,确保输入参数有效Assert(numCols >= 0);// 示例:numCols = 2(region 和 department),断言通过// 结果:确保 numCols = 2 有效// 设置 ExprState 的父节点为传入的 PlanState,用于上下文关联state->parent = parent;// 示例:parent 是 HashAggregate 节点的 PlanState,设置 state->parent// 结果:state->parent = HashAggregate 节点/** 如果有多于一个列需要哈希,或者有一个列且有非零初始值,* 分配内存用于存储中间哈希值,以便在多列计算时进行组合*/if ((int64) numCols + (init_value != 0) > 1)iresult = palloc(sizeof(NullableDatum));// 示例:numCols = 2,init_value = 0,条件 (2 + 0 > 1) 满足,分配 iresult// 结果:iresult 指向新分配的 NullableDatum,value 和 isnull 初始化为 0/* 遍历所有列,找到最大的列编号,以便解构元组到该位置 */for (int i = 0; i < numCols; i++)last_attnum = Max(last_attnum, keyColIdx[i]);// 示例:keyColIdx = [1, 2](region = 1, department = 2)// 循环:i = 0, last_attnum = Max(0, 1) = 1//       i = 1, last_attnum = Max(1, 2) = 2// 结果:last_attnum = 2// 设置操作码为提取部分列值(EEOP_INNER_FETCHSOME),准备从元组中提取数据scratch.opcode = EEOP_INNER_FETCHSOME;// 示例:设置 scratch 的操作码为 EEOP_INNER_FETCHSOME,表示提取元组列// 结果:scratch.opcode = EEOP_INNER_FETCHSOME// 指定需要提取的最大列编号scratch.d.fetch.last_var = last_attnum;// 示例:last_attnum = 2,设置提取到第 2 列(department)// 结果:scratch.d.fetch.last_var = 2// 设置非固定格式,允许动态解构元组scratch.d.fetch.fixed = false;// 示例:元组格式可能动态变化(如虚拟元组),设为非固定// 结果:scratch.d.fetch.fixed = false// 指定元组操作类型(如 TTSOpsMinimalTuple)scratch.d.fetch.kind = ops;// 示例:ops = TTSOpsMinimalTuple(最小元组操作)// 结果:scratch.d.fetch.kind = TTSOpsMinimalTuple// 设置元组描述符,用于定义列的结构scratch.d.fetch.known_desc = desc;// 示例:desc 是 sales 表的元组描述符(region: VARCHAR, department: VARCHAR, amount: INTEGER)// 结果:scratch.d.fetch.known_desc = sales 表的 TupleDesc// 计算元组槽信息并检查是否需要添加提取步骤if (ExecComputeSlotInfo(state, &scratch))// 示例:调用 ExecComputeSlotInfo 检查是否需要解构元组(因 last_var = 2,需解构)// 结果:返回 true,需添加提取步骤// 将提取步骤添加到 ExprState 的执行计划中ExprEvalPushStep(state, &scratch);// 示例:将 scratch(EEOP_INNER_FETCHSOME)推入 state->steps// 结果:state->steps = [EEOP_INNER_FETCHSOME {last_var=2, fixed=false, kind=TTSOpsMinimalTuple, known_desc=desc}]// 如果初始哈希值为 0if (init_value == 0){/** 没有初始值,直接使用第一个列的哈希函数结果,无需与初始值组合* 设置操作码为 EEOP_HASHDATUM_FIRST,表示首次哈希计算*/opcode = EEOP_HASHDATUM_FIRST;// 示例:init_value = 0,设置 opcode 为首次哈希// 结果:opcode = EEOP_HASHDATUM_FIRST}else{/** 设置初始哈希值的操作,存储到中间结果或 ExprState 的结果字段* 如果有列要哈希,存储到中间结果;否则直接存储到 ExprState*/scratch.opcode = EEOP_HASHDATUM_SET_INITVAL;// 示例:此分支不执行(init_value = 0)// 结果:跳过// 将初始值转换为 Datum 类型scratch.d.hashdatum_initvalue.init_value = UInt32GetDatum(init_value);// 示例:不执行// 结果:无// 根据是否有列,选择存储位置(中间结果或最终结果)scratch.resvalue = numCols > 0 ? &iresult->value : &state->resvalue;scratch.resnull = numCols > 0 ? &iresult->isnull : &state->resnull;// 示例:不执行// 结果:无// 将初始值设置步骤添加到执行计划ExprEvalPushStep(state, &scratch);// 示例:不执行// 结果:无/** 使用初始值时,后续哈希计算使用 EEOP_HASHDATUM_NEXT32,* 以避免覆盖初始值(EEOP_HASHDATUM_FIRST 会覆盖)*/opcode = EEOP_HASHDATUM_NEXT32;// 示例:不执行// 结果:无}// 遍历每一列,构建哈希计算的执行步骤for (int i = 0; i < numCols; i++){// 获取当前列的哈希函数信息FmgrInfo   *finfo;// 示例:i = 0 时,finfo 将指向 region 的哈希函数;i = 1 时,指向 department 的哈希函数// 结果:finfo 未赋值// 初始化函数调用信息结构FunctionCallInfo fcinfo;// 示例:fcinfo 将指向新分配的函数调用信息// 结果:fcinfo 未初始化// 获取当前列的排序规则Oid			inputcollid = collations[i];// 示例:i = 0,inputcollid = collations[0](如 C 排序规则)// 结果:inputcollid = C// 列编号从 1 开始,转换为 0 基索引AttrNumber	attnum = keyColIdx[i] - 1;// 示例:i = 0,keyColIdx[0] = 1(region),attnum = 1 - 1 = 0//       i = 1,keyColIdx[1] = 2(department),attnum = 2 - 1 = 1// 结果:attnum = 0(第一次循环),1(第二次循环)// 获取当前列的哈希函数finfo = &hashfunctions[i];// 示例:i = 0,finfo = hashfunctions[0](如 hash_any for VARCHAR)// 结果:finfo = hash_any// 分配并初始化函数调用信息结构,参数数量为 1fcinfo = palloc0(SizeForFunctionCallInfo(1));// 示例:分配 FunctionCallInfo 结构,大小为 1 个参数,初始化为 0// 结果:fcinfo 指向新分配的内存,args 数组清零// 初始化函数调用信息,设置函数、参数数量和排序规则InitFunctionCallInfoData(*fcinfo, finfo, 1, inputcollid, NULL, NULL);// 示例:设置 fcinfo->flinfo = finfo,nargs = 1,fncollation = C// 结果:fcinfo 配置为调用 hash_any,参数数 1,排序规则 C/** 设置提取列值的步骤(EEOP_INNER_VAR),将指定列的值存储到哈希函数的第一个参数*/scratch.opcode = EEOP_INNER_VAR;// 示例:设置操作码为 EEOP_INNER_VAR,提取列值// 结果:scratch.opcode = EEOP_INNER_VAR// 设置存储目标为哈希函数的第一个参数scratch.resvalue = &fcinfo->args[0].value;scratch.resnull = &fcinfo->args[0].isnull;// 示例:将列值存储到 fcinfo->args[0].value,NULL 标志存储到 fcinfo->args[0].isnull// 结果:scratch.resvalue = &fcinfo->args[0].value, scratch.resnull = &fcinfo->args[0].isnull// 设置要提取的列编号scratch.d.var.attnum = attnum;// 示例:i = 0,attnum = 0(region);i = 1,attnum = 1(department)// 结果:scratch.d.var.attnum = 0(第一次),1(第二次)// 设置列的数据类型scratch.d.var.vartype = TupleDescAttr(desc, attnum)->atttypid;// 示例:i = 0,desc[0].atttypid = VARCHAR;i = 1,desc[1].atttypid = VARCHAR// 结果:scratch.d.var.vartype = VARCHAR// 将提取列值的步骤添加到执行计划ExprEvalPushStep(state, &scratch);// 示例:i = 0,添加 EEOP_INNER_VAR {attnum=0, vartype=VARCHAR}//       i = 1,添加 EEOP_INNER_VAR {attnum=1, vartype=VARCHAR}// 结果:state->steps = [EEOP_INNER_FETCHSOME, EEOP_INNER_VAR {region}, EEOP_INNER_VAR {department}]// 设置调用哈希函数的步骤,使用之前确定的操作码scratch.opcode = opcode;// 示例:i = 0,opcode = EEOP_HASHDATUM_FIRST;i = 1,opcode = EEOP_HASHDATUM_NEXT32// 结果:scratch.opcode = EEOP_HASHDATUM_FIRST(第一次),EEOP_HASHDATUM_NEXT32(第二次)// 如果是最后一列if (i == numCols - 1){/** 最后一列的哈希结果直接存储到 ExprState 的结果字段*/scratch.resvalue = &state->resvalue;scratch.resnull = &state->resnull;// 示例:i = 1(department,最后一列),结果存储到 state->resvalue 和 state->resnull// 结果:scratch.resvalue = &state->resvalue, scratch.resnull = &state->resnull}else{// 确保中间结果已分配Assert(iresult != NULL);// 示例:i = 0(region),iresult 已分配// 结果:断言通过// 中间列的哈希结果存储到中间结果中scratch.resvalue = &iresult->value;scratch.resnull = &iresult->isnull;// 示例:i = 0,存储到 iresult->value 和 iresult->isnull// 结果:scratch.resvalue = &iresult->value, scratch.resnull = &iresult->isnull}/** 为 NEXT32 操作码设置中间结果,FIRST 操作码不会使用* 为安全起见,始终设置中间结果指针*/scratch.d.hashdatum.iresult = iresult;// 示例:设置中间结果指针// 结果:scratch.d.hashdatum.iresult = iresult// 设置哈希函数信息scratch.d.hashdatum.finfo = finfo;// 示例:finfo = hash_any// 结果:scratch.d.hashdatum.finfo = hash_any// 设置函数调用信息scratch.d.hashdatum.fcinfo_data = fcinfo;// 示例:fcinfo 包含 hash_any 的调用信息// 结果:scratch.d.hashdatum.fcinfo_data = fcinfo// 设置函数地址scratch.d.hashdatum.fn_addr = finfo->fn_addr;// 示例:fn_addr = hash_any 的函数地址// 结果:scratch.d.hashdatum.fn_addr = hash_any// 设置跳转标志,初始为 -1scratch.d.hashdatum.jumpdone = -1;// 示例:无跳转需求,设为 -1// 结果:scratch.d.hashdatum.jumpdone = -1// 将哈希函数调用步骤添加到执行计划ExprEvalPushStep(state, &scratch);// 示例:i = 0,添加 EEOP_HASHDATUM_FIRST {finfo=hash_any, resvalue=iresult}//       i = 1,添加 EEOP_HASHDATUM_NEXT32 {finfo=hash_any, resvalue=state->resvalue}// 结果:state->steps = [EEOP_INNER_FETCHSOME, EEOP_INNER_VAR {region}, EEOP_HASHDATUM_FIRST, EEOP_INNER_VAR {department}, EEOP_HASHDATUM_NEXT32]// 后续列使用 EEOP_HASHDATUM_NEXT32,以组合前面的哈希值opcode = EEOP_HASHDATUM_NEXT32;// 示例:i = 0 后,opcode 更新为 EEOP_HASHDATUM_NEXT32// 结果:opcode = EEOP_HASHDATUM_NEXT32}// 设置终止步骤,清除结果指针scratch.resvalue = NULL;scratch.resnull = NULL;// 示例:清除结果指针,准备终止步骤// 结果:scratch.resvalue = NULL, scratch.resnull = NULL// 设置操作码为 EEOP_DONE,表示执行计划结束scratch.opcode = EEOP_DONE;// 示例:设置终止操作码// 结果:scratch.opcode = EEOP_DONE// 将终止步骤添加到执行计划ExprEvalPushStep(state, &scratch);// 示例:添加 EEOP_DONE// 结果:state->steps = [EEOP_INNER_FETCHSOME, EEOP_INNER_VAR {region}, EEOP_HASHDATUM_FIRST, EEOP_INNER_VAR {department}, EEOP_HASHDATUM_NEXT32, EEOP_DONE]// 准备 ExprState,使其可执行ExecReadyExpr(state);// 示例:设置 state->evalfunc(如 ExecJustHashVarImpl 或 JIT 编译函数),准备执行// 结果:state->evalfunc 设置,ExprState 可执行// 返回构建完成的 ExprStatereturn state;// 示例:返回包含完整步骤序列的 ExprState// 结果:返回 state
}

最终 ExprState 结构

  对于示例查询,ExecBuildHash32FromAttrs 生成的 ExprState 包含以下步骤序列:

  1. EEOP_INNER_FETCHSOME

    • last_var = 2(提取到 department 列)。
    • fixed = false, kind = TTSOpsMinimalTuple, known_desc = sales 表的 TupleDesc
    • 作用:解构输入元组,准备提取 regiondepartment
  2. EEOP_INNER_VAR (region)

    • attnum = 0, vartype = VARCHAR
    • resvalue = fcinfo[0]->args[0].value, resnull = fcinfo[0]->args[0].isnull
    • 作用:提取 region 列值,存储到第一个哈希函数的参数。
  3. EEOP_HASHDATUM_FIRST

    • finfo = hash_any, fcinfo_data = hash_any 的调用信息, fn_addr = hash_any
    • resvalue = iresult->value, resnull = iresult->isnull
    • 作用:调用 hash_any 计算 region 的哈希值,存储到中间结果。
  4. EEOP_INNER_VAR (department)

    • attnum = 1, vartype = VARCHAR
    • resvalue = fcinfo[1]->args[0].value, resnull = fcinfo[1]->args[0].isnull
    • 作用:提取 department 列值,存储到第二个哈希函数的参数。
  5. EEOP_HASHDATUM_NEXT32

    • finfo = hash_any, fcinfo_data = hash_any 的调用信息, fn_addr = hash_any
    • resvalue = state->resvalue, resnull = state->resnull
    • iresult:指向中间结果,包含 region 的哈希值。
    • 作用:调用 hash_any 计算 department 的哈希值,与中间结果组合,存储最终哈希值。
  6. EEOP_DONE

    • 作用:终止执行计划。

执行示例

  假设处理元组 (region = "North", department = "Sales", amount = 100)

  1. 调用

    • ExecBuildHash32FromAttrs(desc, TTSOpsMinimalTuple, [hash_any, hash_any], [C, C], 2, [1, 2], HashAggregate, 0)
    • 输入:descsales 表的描述符,keyColIdx = [1, 2]regiondepartment),init_value = 0
  2. 执行步骤

    • EEOP_INNER_FETCHSOME:解构元组,提取 regiondepartment
    • EEOP_INNER_VAR (region):提取 "North",存入 fcinfo[0]->args[0].value
    • EEOP_HASHDATUM_FIRST:调用 hash_any("North"),得到哈希值(如 0x12345678),存入 iresult->value
    • EEOP_INNER_VAR (department):提取 "Sales",存入 fcinfo[1]->args[0].value
    • EEOP_HASHDATUM_NEXT32:调用 hash_any("Sales"),得到哈希值(如 0xabcdef12),与 iresult->value 组合(如通过 hash_combine32),存入 state->resvalue
    • EEOP_DONE:结束,state->resvalue 包含最终哈希值(如 0x7890abcd)。
  3. 结果

    • 返回的 ExprStateHashAggregate 节点使用,哈希值 0x7890abcd 用于哈希表插入或查找。

总结

  ExecBuildHash32FromAttrs 通过构建 ExprState 和一系列 ExprEvalStep,为 GROUP BY 的列(regiondepartment)生成高效的哈希计算计划。每个步骤(提取元组、获取列值、调用哈希函数)被精确配置,减少函数调用开销,支持 JIT 编译。示例查询展示了如何从输入参数到最终 ExprState 的构建,覆盖了每行代码的作用,最终生成一个包含提取、哈希和终止步骤的执行计划,用于高效哈希表操作。

ExecComputeSlotInfo

  ExecComputeSlotInfoPostgreSQL 执行器中用于确定元组槽(TupleTableSlot)是否为固定格式的辅助函数,决定是否需要添加解构步骤(EEOP_*_FETCHSOME),常用于哈希计算等场景。以下内容分为两部分:ExecComputeSlotInfo 的代码注释与解释,以及 ExecBuildHash32FromAttrs 的流程图代码。


示例 SQL 查询

与之前一致,使用以下查询作为上下文:

SELECT region, department, SUM(amount)
FROM sales
GROUP BY region, department;
  • 表结构

    • sales 包含列:region (VARCHAR), department (VARCHAR), amount (INTEGER)。
    • 示例数据:
      region  | department | amount
      --------+------------+-------
      North   | Sales      | 100
      South   | Marketing  | 200
      North   | Marketing  | 150
      
  • 查询目标:按 regiondepartment 分组,计算 amount 的总和。

  • 上下文ExecBuildHash32FromAttrs 调用 ExecComputeSlotInfo 来确定是否需要添加 EEOP_INNER_FETCHSOME 步骤,用于提取 regiondepartment 的值以进行哈希计算。


函数注释与解释

  以下是 PostgreSQLExecComputeSlotInfo 函数的每行代码,添加中文注释并结合 SQL 查询 SELECT region, department, SUM(amount) FROM sales GROUP BY region, department 解释其作用。
  该函数用于确定元组槽(TupleTableSlot)是否为固定格式(fixed),以决定是否需要添加解构步骤(EEOP_*_FETCHSOME,在哈希计算(如 ExecBuildHash32FromAttrs)中确保正确提取列值。

/** 计算 EEOP_*_FETCHSOME 操作的附加信息。** 目标是确定元组槽是否为“固定”格式,即每次表达式求值时槽类型和描述符是否保持一致。** 返回 true 表示需要解构步骤,返回 false 表示不需要。*/
static bool
ExecComputeSlotInfo(ExprState *state, ExprEvalStep *op)
{// 获取 ExprState 的父节点(PlanState),用于访问计划信息PlanState  *parent = state->parent;// 初始化元组描述符,存储槽的列结构TupleDesc	desc = NULL;// 初始化元组槽操作类型(如 TTSOpsMinimalTuple)const TupleTableSlotOps *tts_ops = NULL;// 标志位,指示槽是否为固定格式bool		isfixed = false;// 获取当前操作码(EEOP_INNER_FETCHSOME、EEOP_OUTER_FETCHSOME 或 EEOP_SCAN_FETCHSOME)ExprEvalOp	opcode = op->opcode;// 断言操作码为提取操作,确保输入有效Assert(opcode == EEOP_INNER_FETCHSOME ||opcode == EEOP_OUTER_FETCHSOME ||opcode == EEOP_SCAN_FETCHSOME);// 示例:opcode = EEOP_INNER_FETCHSOME(由 ExecBuildHash32FromAttrs 设置)// 结果:断言通过// 如果已知元组描述符(known_desc)不为空if (op->d.fetch.known_desc != NULL){// 使用已知的描述符desc = op->d.fetch.known_desc;// 使用已知的槽操作类型tts_ops = op->d.fetch.kind;// 如果 kind 不为空,槽格式固定isfixed = op->d.fetch.kind != NULL;// 示例:op->d.fetch.known_desc = sales 表描述符,kind = TTSOpsMinimalTuple// 结果:desc = sales 表 TupleDesc,tts_ops = TTSOpsMinimalTuple,isfixed = true}// 如果没有已知描述符且无父节点else if (!parent){// 槽格式非固定isfixed = false;// 示例:parent = HashAggregate 节点,此分支不执行// 结果:无}// 如果操作码为 EEOP_INNER_FETCHSOMEelse if (opcode == EEOP_INNER_FETCHSOME){// 获取内层计划状态PlanState  *is = innerPlanState(parent);// 示例:is = 扫描 sales 表的 Scan 节点// 结果:is = SeqScan 节点// 如果内层操作已设置但非固定格式if (parent->inneropsset && !parent->inneropsfixed){// 槽格式非固定isfixed = false;// 示例:inneropsfixed = true(通常固定),此分支不执行// 结果:无}// 如果内层操作已设置且有操作类型else if (parent->inneropsset && parent->innerops){// 槽格式固定isfixed = true;// 使用内层操作类型tts_ops = parent->innerops;// 获取内层计划的结果描述符desc = ExecGetResultType(is);// 示例:innerops = TTSOpsMinimalTuple,desc = sales 表描述符// 结果:isfixed = true,tts_ops = TTSOpsMinimalTuple,desc = sales 表 TupleDesc}// 如果存在内层计划else if (is){// 获取内层计划的槽操作类型和固定标志tts_ops = ExecGetResultSlotOps(is, &isfixed);// 获取内层计划的结果描述符desc = ExecGetResultType(is);// 示例:is = SeqScan 节点,tts_ops = TTSOpsMinimalTuple,isfixed = true// 结果:tts_ops = TTSOpsMinimalTuple,desc = sales 表 TupleDesc,isfixed = true}}// 如果操作码为 EEOP_OUTER_FETCHSOMEelse if (opcode == EEOP_OUTER_FETCHSOME){// 获取外层计划状态PlanState  *os = outerPlanState(parent);// 示例:无外层计划(单表查询),此分支不执行// 结果:无// 如果外层操作已设置但非固定格式if (parent->outeropsset && !parent->outeropsfixed){isfixed = false;}// 如果外层操作已设置且有操作类型else if (parent->outeropsset && parent->outerops){isfixed = true;tts_ops = parent->outerops;desc = ExecGetResultType(os);}// 如果存在外层计划else if (os){tts_ops = ExecGetResultSlotOps(os, &isfixed);desc = ExecGetResultType(os);}}// 如果操作码为 EEOP_SCAN_FETCHSOMEelse if (opcode == EEOP_SCAN_FETCHSOME){// 使用父节点的扫描描述符desc = parent->scandesc;// 示例:scandesc = sales 表描述符,此分支不执行(opcode = EEOP_INNER_FETCHSOME)// 结果:无// 如果扫描操作已设置if (parent->scanops)tts_ops = parent->scanops;// 如果扫描操作已设置固定标志if (parent->scanopsset)isfixed = parent->scanopsfixed;}// 如果槽格式固定且描述符和操作类型不为空if (isfixed && desc != NULL && tts_ops != NULL){// 设置槽为固定格式op->d.fetch.fixed = true;// 设置槽操作类型op->d.fetch.kind = tts_ops;// 设置已知描述符op->d.fetch.known_desc = desc;// 示例:isfixed = true,desc = sales 表 TupleDesc,tts_ops = TTSOpsMinimalTuple// 结果:op->d.fetch.fixed = true,kind = TTSOpsMinimalTuple,known_desc = sales 表 TupleDesc}else{// 设置槽为非固定格式op->d.fetch.fixed = false;// 清空槽操作类型op->d.fetch.kind = NULL;// 清空描述符op->d.fetch.known_desc = NULL;// 示例:此分支不执行(isfixed = true)// 结果:无}// 如果槽固定且为虚拟槽(TTSOpsVirtual),无需解构if (op->d.fetch.fixed && op->d.fetch.kind == &TTSOpsVirtual)return false;// 示例:kind = TTSOpsMinimalTuple,非虚拟槽// 结果:不返回 false,继续检查// 返回 true,表示需要解构步骤return true;// 示例:kind != TTSOpsVirtual,返回 true// 结果:返回 true,需添加 EEOP_INNER_FETCHSOME 步骤
}

作用

  ExecComputeSlotInfoPostgreSQL 执行器中的辅助函数,用于为 EEOP_*_FETCHSOME 操作(提取元组列值的步骤)计算元组槽的附加信息,确定槽是否为“固定”格式(即每次求值时槽类型和描述符一致)。它在 ExecBuildHash32FromAttrs 中被调用,决定是否需要添加 EEOP_INNER_FETCHSOME 步骤来解构元组(如提取 regiondepartment 的值)。其核心目标是:

  • 确定槽类型:检查槽是否为固定格式(fixed),并获取槽操作类型(tts_ops)和描述符(desc)。
  • 优化性能:如果槽为虚拟槽(TTSOpsVirtual),无需解构,返回 false,减少步骤;否则返回 true,添加解构步骤。

参数

  • stateExprState *,表达式执行计划,包含父节点(parent)信息。
  • opExprEvalStep *,当前操作步骤,包含 opcode(如 EEOP_INNER_FETCHSOME)和 fetch 子结构(known_desc, kind, fixed)。

返回值

  • booltrue 表示需要解构步骤(添加 EEOP_*_FETCHSOME),false 表示无需解构。

执行流程(结合示例)

  1. 初始化

    • 获取 parentHashAggregate 节点),opcodeEEOP_INNER_FETCHSOME),初始化 desctts_opsisfixed
    • 示例:parentHashAggregateopcode = EEOP_INNER_FETCHSOME
  2. 检查已知描述符

    • 如果 op->d.fetch.known_desc 不为空,直接使用已知的 desctts_ops,设置 isfixed
    • 示例:known_desc = sales 表 TupleDesckind = TTSOpsMinimalTupleisfixed = true
  3. 处理内层计划(EEOP_INNER_FETCHSOME

    • 获取内层计划(SeqScan 节点),检查 parent->inneropssetinneropsfixed
    • 示例:innerops = TTSOpsMinimalTupleinneropsfixed = truedesc = sales 表 TupleDesc
  4. 设置槽信息

    • 如果 isfixed = truedesctts_ops 不为空,设置 op->d.fetch.fixed = truekind = TTSOpsMinimalTupleknown_desc = sales 表 TupleDesc
    • 示例:设置 op->d.fetch 为固定格式,kind = TTSOpsMinimalTuple
  5. 检查虚拟槽

    • 如果槽固定且为 TTSOpsVirtual,返回 false(无需解构)。
    • 示例:kind = TTSOpsMinimalTuple,非虚拟槽,返回 true
  6. 返回结果

    • 示例:返回 true,表示需要添加 EEOP_INNER_FETCHSOME 步骤。

ExecBuildHash32FromAttrs 的关系

  在 ExecBuildHash32FromAttrsExecComputeSlotInfo 被调用以确定是否需要 EEOP_INNER_FETCHSOME 步骤:

  • 输入state 是新建的 ExprStateopscratchopcode = EEOP_INNER_FETCHSOME, last_var = 2, kind = TTSOpsMinimalTuple, known_desc = sales 表 TupleDesc)。
  • 输出:返回 true,触发 ExprEvalPushStep(state, &scratch),添加解构步骤。
  • 作用:确保 regiondepartment 的值被正确提取,供后续哈希计算使用。

功能结构流程图

  以下是 ExecBuildHash32FromAttrs 的 Mermaid 流程图代码,结合 ExecComputeSlotInfo 的调用,展示其完整功能结构。流程图采用纵向布局(TD),使用简洁中文描述,节点为矩形(操作)或菱形(条件),颜色区分不同阶段,布局合理,清晰展示构建 ExprState 的过程。

开始: ExecBuildHash32FromAttrs
初始化 ExprState 和 scratch
设置 parent 和 last_attnum
numCols + init_value > 1?
分配中间结果 iresult
添加 EEOP_INNER_FETCHSOME
调用 ExecComputeSlotInfo
init_value == 0?
设置 opcode = EEOP_HASHDATUM_FIRST
添加 EEOP_HASHDATUM_SET_INITVAL
opcode = EEOP_HASHDATUM_NEXT32
遍历每列 keyColIdx
设置 EEOP_INNER_VAR
提取列值到 fcinfo->args
最后一列?
设置结果到 state->resvalue
设置结果到 iresult
添加哈希步骤
EEOP_HASHDATUM_FIRST/NEXT32
更新 opcode = EEOP_HASHDATUM_NEXT32
更多列?
添加 EEOP_DONE
调用 ExecReadyExpr
返回 ExprState
结束

流程图说明

  • 布局:纵向(TD),从上到下展示 ExecBuildHash32FromAttrs 的逻辑流程,清晰简洁。
  • 节点
    • 矩形表示操作(如初始化、提取列值),菱形表示条件(如 numCols + init_value > 1)。
    • 节点分组:初始化(init,绿色)、条件(condition,黄色)、循环(loop,浅红)、提取/设置(extract/setup,绿色)、存储/哈希(store/hash,蓝色)、结束(finish,灰色)。
  • 逻辑
    • 初始化 ExprStatescratch,设置 parentlast_attnum
    • 检查是否需要中间结果(iresult)。
    • 调用 ExecComputeSlotInfo 添加 EEOP_INNER_FETCHSOME
    • 根据 init_value 设置初始哈希操作(EEOP_HASHDATUM_FIRSTSET_INITVAL)。
    • 循环每列,添加 EEOP_INNER_VAREEOP_HASHDATUM_FIRST/NEXT32,存储结果到 iresultstate->resvalue
    • 添加 EEOP_DONE,调用 ExecReadyExpr,返回 ExprState
  • 与 ExecComputeSlotInfo 的关联
    • 在节点 F(EEOP_INNER_FETCHSOME),调用 ExecComputeSlotInfo 确定槽格式,决定是否添加解构步骤。
    • 示例中,ExecComputeSlotInfo 返回 true,触发添加 EEOP_INNER_FETCHSOME {last_var=2, kind=TTSOpsMinimalTuple}

示例执行总结

对于查询 SELECT region, department, SUM(amount) FROM sales GROUP BY region, department

  1. ExecBuildHash32FromAttrs 调用

    • 输入:desc = sales 表 TupleDesc, ops = TTSOpsMinimalTuple, hashfunctions = [hash_any, hash_any], collations = [C, C], numCols = 2, keyColIdx = [1, 2], parent = HashAggregate, init_value = 0
    • 过程:
      • 初始化 ExprStatelast_attnum = 2
      • 调用 ExecComputeSlotInfo,确认槽为固定格式(TTSOpsMinimalTuple),返回 true,添加 EEOP_INNER_FETCHSOME
      • region 添加 EEOP_INNER_VAREEOP_HASHDATUM_FIRST(结果存 iresult)。
      • department 添加 EEOP_INNER_VAREEOP_HASHDATUM_NEXT32(结果存 state->resvalue)。
      • 添加 EEOP_DONE,调用 ExecReadyExpr
    • 输出:ExprState 包含步骤 [EEOP_INNER_FETCHSOME, EEOP_INNER_VAR {region}, EEOP_HASHDATUM_FIRST, EEOP_INNER_VAR {department}, EEOP_HASHDATUM_NEXT32, EEOP_DONE]
  2. ExecComputeSlotInfo 作用

    • 确认槽为 TTSOpsMinimalTuple,固定格式,返回 true,确保 EEOP_INNER_FETCHSOME 正确解构元组,提取 regiondepartment
  3. 最终效果

    • 生成的 ExprState 用于 HashAggregate,为每行元组(如 ("North", "Sales", 100))计算组合哈希值,支持高效分组。

总结

  ExecComputeSlotInfoExecBuildHash32FromAttrs 的关键辅助函数,通过确定元组槽的固定性和类型,确保正确添加解构步骤(EEOP_INNER_FETCHSOME),为哈希计算准备列值。结合示例查询,ExecComputeSlotInfo 返回 true,触发解构步骤的添加,而 ExecBuildHash32FromAttrs 构建完整的 ExprState,包含提取、哈希和终止步骤,支持高效哈希表操作和 JIT 编译。流程图直观展示了 ExecBuildHash32FromAttrs 的逻辑,突出 ExecComputeSlotInfo 的作用。

为了帮助你理解 ExprEvalPushStep 函数的运行流程,我将结合 ExecBuildHash32FromAttrs 的上下文,通过之前的 SQL 查询示例,逐行解释 ExprEvalPushStep 的代码,展示其在哈希计算中如何向 ExprState 添加执行步骤。同时,我会为 ExecBuildHash32FromAttrs 提供一个补充的 Mermaid 流程图代码,进一步澄清其逻辑流程,确保与 ExprEvalPushStep 的调用关系清晰。ExprEvalPushStep 是 PostgreSQL 执行器中用于向 ExprState 的步骤数组添加新 ExprEvalStep 的辅助函数,广泛用于构建表达式执行计划。


ExprEvalPushStep

示例 SQL 查询(上下文)

  继续使用之前的查询:

SELECT region, department, SUM(amount)
FROM sales
GROUP BY region, department;
  • 表结构

    • sales 包含列:region (VARCHAR), department (VARCHAR), amount (INTEGER).
    • 示例数据:
      region  | department | amount
      --------+------------+-------
      North   | Sales      | 100
      South   | Marketing  | 200
      North   | Marketing  | 150
      
  • 查询目标:按 regiondepartment 分组,计算 amount 的总和。

  • 上下文ExecBuildHash32FromAttrs 调用 ExprEvalPushStep 将多个 ExprEvalStep(如 EEOP_INNER_FETCHSOME, EEOP_INNER_VAR, EEOP_HASHDATUM_FIRST, EEOP_HASHDATUM_NEXT32, EEOP_DONE)添加到 ExprState->steps,构建哈希计算的执行计划。


函数注释与解释

  以下是 PostgreSQLExprEvalPushStep 函数的每行代码,添加中文注释并结合 SQL 查询 SELECT region, department, SUM(amount) FROM sales GROUP BY region, department 解释其作用。该函数用于向 ExprState 的步骤数组(steps)添加新的 ExprEvalStep,支持动态扩展数组,确保构建表达式执行计划(如哈希计算)的正确性。

/** 向 ExprState->steps 添加一个新的表达式求值步骤。** 注意:此操作可能重新分配 es->steps 数组,因此在构建表达式期间,* 不得使用指向该数组的指针。*/
void
ExprEvalPushStep(ExprState *es, const ExprEvalStep *s)
{// 如果步骤数组未分配(初始为空)if (es->steps_alloc == 0){// 初始化分配大小为 16es->steps_alloc = 16;// 分配内存,存储 16 个 ExprEvalStepes->steps = palloc(sizeof(ExprEvalStep) * es->steps_alloc);// 示例:在 ExecBuildHash32FromAttrs 中,首次调用时 es->steps_alloc = 0// 结果:es->steps_alloc = 16,es->steps 指向新分配的内存,可存 16 个步骤}// 如果步骤数组已满(当前长度等于分配大小)else if (es->steps_alloc == es->steps_len){// 将分配大小加倍es->steps_alloc *= 2;// 重新分配内存,扩展为新的大小es->steps = repalloc(es->steps,sizeof(ExprEvalStep) * es->steps_alloc);// 示例:当 steps_len = 16 时,扩展到 32// 结果:es->steps_alloc = 32,es->steps 指向扩展后的内存}// 将新的 ExprEvalStep 复制到 steps 数组的末尾,并增加长度memcpy(&es->steps[es->steps_len++], s, sizeof(ExprEvalStep));// 示例:添加 EEOP_INNER_FETCHSOME,es->steps_len 从 0 增到 1// 结果:es->steps[0] = s(EEOP_INNER_FETCHSOME),es->steps_len = 1
}

作用

  ExprEvalPushStepPostgreSQL 执行器中的辅助函数,用于ExprStatesteps 数组动态添加新的 ExprEvalStep,支持构建表达式执行计划(如哈希计算、投影)。
  它确保数组大小足够,并在需要时扩展内存,广泛应用于 ExecBuildHash32FromAttrs 等函数中,添加步骤如 EEOP_INNER_FETCHSOME, EEOP_INNER_VAR, EEOP_HASHDATUM_FIRST, EEOP_HASHDATUM_NEXT32, EEOP_DONE

  • 动态分配:通过 pallocrepalloc 管理 steps 数组,初始分配 16 个步骤,数组满时加倍扩展。
  • 内存安全:注释警告在构建期间不得使用 steps 数组的指针,因为 repalloc 可能改变数组地址。
  • 上下文:在哈希计算中,ExecBuildHash32FromAttrs 调用 ExprEvalPushStep 多次,构建完整的哈希计算计划。

参数

  • esExprState *表达式执行计划,包含 steps(步骤数组)、steps_len(当前长度)、steps_alloc(分配大小)。
  • sconst ExprEvalStep *要添加的步骤,包含 opcode(如 EEOP_INNER_FETCHSOME)和相关数据(如 fetch, var, hashdatum)。

返回值

  • void无返回值,直接修改 es->steps 数组。

执行流程(结合示例)

  假设 ExecBuildHash32FromAttrsregiondepartment 构建哈希计算的 ExprState,调用 ExprEvalPushStep 添加步骤:

  1. 初始化检查

    • 检查 es->steps_alloc 是否为 0。
    • 示例:首次调用时,es->steps_alloc = 0,分配 16 个步骤的内存,es->steps 指向新内存,es->steps_alloc = 16
  2. 扩展检查

    • 如果 es->steps_len == es->steps_alloc,扩展数组。
    • 示例steps_len = 0steps_alloc = 16,无需扩展。后续若 steps_len = 16,则扩展到 steps_alloc = 32
  3. 添加步骤

    • 复制 ses->steps[es->steps_len]steps_len 增 1。
    • 示例:添加 EEOP_INNER_FETCHSOMEes->steps[0] = ssteps_len = 1
  4. 多次调用

    • ExecBuildHash32FromAttrs 调用多次,添加 [EEOP_INNER_FETCHSOME, EEOP_INNER_VAR {region}, EEOP_HASHDATUM_FIRST, EEOP_INNER_VAR {department}, EEOP_HASHDATUM_NEXT32, EEOP_DONE]
    • 示例:每次调用添加一个步骤,steps_len 从 0 增到 6,steps 包含完整计划。

示例调用

  • 首次调用(添加 EEOP_INNER_FETCHSOME):
    • 输入:es->steps_alloc = 0, es->steps_len = 0, s = {opcode = EEOP_INNER_FETCHSOME, last_var = 2, kind = TTSOpsMinimalTuple}
    • 执行:分配 es->steps16 个步骤),复制 ses->steps[0]es->steps_len = 1
  • 后续调用(如添加 EEOP_INNER_VAR):
    • 输入:es->steps_alloc = 16, es->steps_len = 1, s = {opcode = EEOP_INNER_VAR, attnum = 0}
    • 执行:复制 ses->steps[1]es->steps_len = 2
  • 结果:最终 es->steps 包含 6 个步骤,steps_len = 6

功能结构流程图(补充)

  以下是 ExecBuildHash32FromAttrsMermaid 流程图代码,结合 ExprEvalPushStepExecComputeSlotInfo 的调用,展示其完整功能结构。流程图采用纵向布局(TD),使用简洁中文描述,节点为矩形(操作)或菱形(条件),颜色区分不同阶段,清晰展示构建 ExprState 的过程,包括 ExprEvalPushStep 的作用。

开始: ExecBuildHash32FromAttrs
初始化 ExprState 和 scratch
设置 parent 和 last_attnum
numCols + init_value > 1?
分配中间结果 iresult
调用 ExecComputeSlotInfo
添加 EEOP_INNER_FETCHSOME
使用 ExprEvalPushStep
init_value == 0?
设置 opcode = EEOP_HASHDATUM_FIRST
添加 EEOP_HASHDATUM_SET_INITVAL
使用 ExprEvalPushStep
opcode = EEOP_HASHDATUM_NEXT32
遍历每列 keyColIdx
设置 EEOP_INNER_VAR
提取列值到 fcinfo->args
最后一列?
设置结果到 state->resvalue
设置结果到 iresult
添加哈希步骤
EEOP_HASHDATUM_FIRST/NEXT32
使用 ExprEvalPushStep
更新 opcode = EEOP_HASHDATUM_NEXT32
更多列?
添加 EEOP_DONE
使用 ExprEvalPushStep
调用 ExecReadyExpr
返回 ExprState
结束

流程图说明

  • 布局:纵向(TD),从上到下展示 ExecBuildHash32FromAttrs 的逻辑流程,清晰简洁。
  • 节点
    • 矩形表示操作(如初始化、提取列值),菱形表示条件(如 numCols + init_value > 1)。
    • 节点分组:初始化(init,绿色)、条件(condition,黄色)、循环(loop,浅红)、提取/设置(extract/setup,绿色)、存储/哈希(store/hash,蓝色)、结束(finish,灰色)。
  • 逻辑
    • 初始化 ExprStatescratch,设置 parentlast_attnum
    • 检查是否需要中间结果(iresult)。
    • 调用 ExecComputeSlotInfo,根据返回结果添加 EEOP_INNER_FETCHSOME(通过 ExprEvalPushStep)。
    • 根据 init_value 设置初始哈希操作(EEOP_HASHDATUM_FIRSTSET_INITVAL)。
    • 循环每列,添加 EEOP_INNER_VAREEOP_HASHDATUM_FIRST/NEXT32(通过 ExprEvalPushStep)。
      • 添加 EEOP_DONE(通过 ExprEvalPushStep),调用 ExecReadyExpr,返回 ExprState
  • 与 ExprEvalPushStep 的关联
    • 节点 F(EEOP_INNER_FETCHSOME)、I(EEOP_HASHDATUM_SET_INITVAL)、O(EEOP_HASHDATUM_FIRST/NEXT32)、R(EEOP_DONE)调用 ExprEvalPushStep 添加步骤。
    • 示例中,ExprEvalPushStep 被调用 6 次,构建 [EEOP_INNER_FETCHSOME, EEOP_INNER_VAR {region}, EEOP_HASHDATUM_FIRST, EEOP_INNER_VAR {department}, EEOP_HASHDATUM_NEXT32, EEOP_DONE]

示例执行总结

  对于查询 SELECT region, department, SUM(amount) FROM sales GROUP BY region, department

  1. ExecBuildHash32FromAttrs 调用

    • 输入:desc = sales 表 TupleDesc, ops = TTSOpsMinimalTuple, hashfunctions = [hash_any, hash_any], collations = [C, C], numCols = 2, keyColIdx = [1, 2], parent = HashAggregate, init_value = 0
    • 过程:
      • 初始化 ExprStatees->steps_alloc = 0, steps_len = 0
      • 调用 ExecComputeSlotInfo,返回 true,触发 ExprEvalPushStep 添加 EEOP_INNER_FETCHSOMEes->steps[0], steps_len = 1)。
      • region 添加 EEOP_INNER_VAREEOP_HASHDATUM_FIRSTsteps_len = 3)。
      • department 添加 EEOP_INNER_VAREEOP_HASHDATUM_NEXT32steps_len = 5)。
      • 添加 EEOP_DONEsteps_len = 6)。
      • 调用 ExecReadyExpr,设置 es->evalfunc(如 ExecJustHashVarImpl)。
    • 输出:ExprState 包含 6 个步骤。
  2. ExprEvalPushStep 作用

    • 每次调用动态扩展 es->steps(初始分配 16,必要时扩展到 32)。
    • 示例:添加 EEOP_INNER_FETCHSOME 时,es->steps[0] 存储步骤,steps_len 从 0 增到 1;后续步骤类似。
  3. 最终效果

    • 生成的 ExprState 用于 HashAggregate,为每行元组(如 ("North", "Sales", 100))计算组合哈希值,步骤序列高效执行,支持 JIT 编译。

总结

  ExprEvalPushStepExecBuildHash32FromAttrs 的关键辅助函数,通过动态分配和扩展 ExprState->steps 数组,添加哈希计算的执行步骤(如 EEOP_INNER_FETCHSOME, EEOP_INNER_VAR, EEOP_HASHDATUM_*)。结合示例查询,ExprEvalPushStep 被调用 6 次,构建完整的哈希计算计划。流程图直观展示了 ExecBuildHash32FromAttrs 的逻辑,突出 ExprEvalPushStep 在添加步骤中的作用,确保计划构建的内存安全和高效性。

http://www.xdnf.cn/news/1379719.html

相关文章:

  • MySQL 与 ClickHouse 深度对比:架构、性能与场景选择指南
  • 【第三方网站运行环境测试:服务器配置(如Nginx/Apache)的WEB安全测试重点】
  • R 语言 ComplexUpset 包实战:替代 Venn 图的高级集合可视化方案
  • 基于mac的智能语音处理与应用开发-环境部署
  • HTML应用指南:利用POST请求获取全国中国工商银行网点位置信息
  • 【mysql】SQL HAVING子句详解:分组过滤的正确姿势
  • TUN模式端口冲突 启动失败如何解决?
  • 点评项目(Redis中间件)第二部分Redis基础
  • PostgreSQL 流复制与逻辑复制性能优化与故障切换实战经验分享
  • Java集合操作:Apache Commons Collections4启示录
  • 【Web】JWT(JSON Web Token)技术详解
  • 客户案例 | 柳钢集团×甄知科技,燕千云ITSM打造智能服务新生态
  • Mac 开发环境与配置操作速查表
  • 基于django的梧桐山水智慧旅游平台设计与开发(代码+数据库+LW)
  • 破译心智密码:神经科学如何为下一代自然语言处理绘制语义理解的蓝图
  • 磁力计校准矩阵求解方法解析
  • 从体验到系统工程丨上手评测国内首款 AI 电商 App
  • 图书管理系统练习项目源码-前后端分离-【Java版】
  • Python Imaging Library (PIL) 全面指南:PIL基础入门-图像滤波与处理技术
  • week5-[一维数组]去重
  • 机器学习基本概述
  • STM32F407与LAN8720A以太网通信实现指南
  • GraphRAG技术深度解析:重新定义智能问答的未来
  • 【赵渝强老师】MySQL数据库的多实例环境
  • Redis 连接数爆炸:连接池配置错误踩坑记录
  • Electron 简介:Node.js 桌面开发的起点
  • 华为云OBS+HMS+EMRonEC2+HiveSparkFlink+GaussDB
  • QT 概述(背景介绍、搭建开发环境、Qt Creator、程序、项目文件解析、编程注意事项)
  • 隐语Kuscia正式发布 1.0.0 版本,实现支持 Hive 数据源,支持 envoy 日志进行异常分析等功能
  • 银河麒麟桌面操作系统:为什么不让root直接登录图形界面?以及如何安全地解决这个问题