Postgresql源码(130)ExecInterpExpr转换为IR的流程

相关
《Postgresql源码(127)投影ExecProject的表达式执行分析》
《Postgresql源码(128)深入分析JIT中的函数内联llvm_inline》
《Postgresql源码(129)JIT函数中如何使用PG的类型llvmjit_types》

表达式计算在之前做过很多相关的分析了,本篇主要关注ExecInterpExpr如何转换为IR。

PG的表达式计算方法在7年前有一次重构,一方面带来了很大的性能提升,一方面为JIT做准备。

1 为什么PG要重构表达式计算逻辑,会带来哪些提升?

重构在这个提交:b8d7f053c5c2bf2a7e8734fe3327f6a8bc711755

先看下原来的表达式计算长什么样子(左侧)
在这里插入图片描述
原来的表达式计算根据node类型不同配置了大量处理函数,运行时按类型走自己的evalfunc,比如Const类型就会走ExecEvalConst函数计算。

而优化后大部分类型的evalfunc都只使用一个函数:ExecInterpExpr。且在ExecInterpExpr中使用GOTO替换了应该有的大switch逻辑。

这样做对性能会有比较大的提升的原因from commit message

  • non-recursive implementation reduces stack usage / overhead
  • simple sub-expressions are implemented with a single jump, without
    function calls
  • sharing some state between different sub-expressions
  • reduced amount of indirect/hard to predict memory accesses by laying out operation metadata sequentially; including the avoidance of nearly all of the previously used linked lists
  • more code has been moved to expression initialization, avoiding constant re-checks at evaluation time
  1. 非递归实现减少了栈的使用和开销。
  2. 简单子表达式通过单一跳转实现,无需函数调用。
  3. 在不同子表达式之间共享一些状态。
  4. 通过顺序排列操作元数据,减少了间接/难以预测的内存访问;包括避免了几乎所有之前使用的链表
    更多的代码已经移动到表达式初始化阶段,避免了在评估时的不断重新检查。

在我看来还有几点也比较重要

  1. 减少分支预测失败:处理器使用分支预测来猜测程序的控制流路径。switch可能会导致分支预测失败,特别是当有大量的标签时。goto 可以减少分支预测的复杂性,因为控制流更直接。
  2. 更高的指令缓存效率:连续goto应该更容易被处理器的指令缓存。比如跳转的比较近的时候,局部指令可能都在缓存中。而且switch的指令数比goto要多一些。
  3. 代码生成优化:编译器看到goto能做出更多的优化,为后续的JIT实现做准备。

2 生成JIT表达式llvm_compile_expr逻辑分析

还是参考这篇中的例子:《Postgresql源码(128)深入分析JIT中的函数内联llvm_inline》

select abs(k),abs(k),abs(k),abs(k),abs(k),exp(k),exp(k),exp(k),exp(k),exp(k) from t1;

表达式计算投影列时,ExecInterpExpr的步骤:

(gdb) p/x state->steps[0]->opcode
$15 = 0x74b0ad                       EEOP_SCAN_FETCHSOME:取一行
(gdb) p/x state->steps[1]->opcode
$16 = 0x74b1ec                       EEOP_SCAN_VAR:拿到目标列
(gdb) p/x state->steps[2]->opcode
$17 = 0x74b784                       EEOP_FUNCEXPR_STRICT:函数计算
(gdb) p/x state->steps[3]->opcode
$18 = 0x74b591                       EEOP_ASSIGN_TMP:暂存结果
(gdb) p/x state->steps[4]->opcode
$19 = 0x74b1ec                       EEOP_SCAN_VAR:拿到目标列
(gdb) p/x state->steps[5]->opcode
$20 = 0x74b784                       EEOP_FUNCEXPR_STRICT:函数计算
(gdb) p/x state->steps[6]->opcode
$21 = 0x74b591                       EEOP_ASSIGN_TMP:暂存结果
(gdb) p/x state->steps[7]->opcode
$22 = 0x74b1ec                       EEOP_SCAN_VAR:拿到目标列
...
...
(gdb) p/x state->steps[34]->opcode
$27 = 0x74b784                       EEOP_FUNCEXPR_STRICT:函数计算
(gdb) p/x state->steps[35]->opcode
$28 = 0x74b591                       EEOP_ASSIGN_TMP:暂存结果
(gdb) p/x state->steps[36]->opcode
$29 = 0x74b01a                       EEOP_DONE:计算结束

2.1 计算准备

原函数ExecInterpExpr:

static Datum
ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
{ExprEvalStep *op;TupleTableSlot *resultslot;TupleTableSlot *innerslot;TupleTableSlot *outerslot;TupleTableSlot *scanslot;op = state->steps;resultslot = state->resultslot;innerslot = econtext->ecxt_innertuple;outerslot = econtext->ecxt_outertuple;scanslot = econtext->ecxt_scantuple;EEO_DISPATCH();{EEO_CASE(EEOP_DONE){goto out;}EEO_CASE(EEOP_INNER_FETCHSOME){...EEO_NEXT();}EEO_CASE(EEOP_OUTER_FETCHSOME){...EEO_NEXT();}...
out:*isnull = state->resnull;return state->resvalue;
}

原函数的逻辑可以简单理解为循环执行steps,每一个steps走大switch的一个分支(这里已经优化成了goto)。

注意原函数是执行,到jit逻辑中,这里的执行变成了→BUILD IR。

bool
llvm_compile_expr(ExprState *state)
{...
  1. 在context中拿到module,用来存放function
  2. 在context中创建一个builder,用来构造后面的function内容
	mod = llvm_mutable_module(context);lc = LLVMGetModuleContext(mod);b = LLVMCreateBuilderInContext(lc);
  1. 创建函数evalexpr,llvm_pg_var_func_type用来拿到ExecInterpExprStillValid函数的入参(ExprState *state, ExprContext *econtext, bool *isNull)
  2. 增加编译选项LLVMExternalLinkage,指定当前函数可以被其他编译单元看到,所以在link时其他编译单元可以直接使用这里的代码,类似于extern函数。
  3. LLVMAddFunction在mod中增加了一个函数声明evalexpr。
  4. llvm_copy_attributes的功能见《Postgresql源码(129)JIT函数中如何使用PG的类型llvmjit_types》
	funcname = llvm_expand_funcname(context, "evalexpr");eval_fn = LLVMAddFunction(mod, funcname,llvm_pg_var_func_type("ExecInterpExprStillValid"));LLVMSetLinkage(eval_fn, LLVMExternalLinkage);LLVMSetVisibility(eval_fn, LLVMDefaultVisibility);llvm_copy_attributes(AttributeTemplate, eval_fn);
  • 这里给函数增加了第一个Block,函数定义开始了:
	entry = LLVMAppendBasicBlockInContext(lc, eval_fn, "entry");/* build state */v_state = LLVMGetParam(eval_fn, 0);v_econtext = LLVMGetParam(eval_fn, 1);v_isnullp = LLVMGetParam(eval_fn, 2);LLVMPositionBuilderAtEnd(b, entry);
  • 下面执行的操作等价与scanslot = econtext->ecxt_scantuple;从结构体中拿一个成员变量的值。
  • IR中的结构体是不会记录成员名称的,所以需要告知llvm成员变量在结构体中的偏移位置FIELDNO_EXPRCONTEXT_SCANTUPLE = 1。
  • LLVMBuildLoad从内存中加载值。
  • LLVMStructGetTypeAtIndex拿到结构体指定位置的类型。
  • LLVMBuildStructGEP拿到结构体1位置的成员地址(GEP=GetElementPtr)
  • 从API调用的角度等价与:在这里插入图片描述
	v_scanslot = l_load_struct_gep(b,StructExprContext,v_econtext,FIELDNO_EXPRCONTEXT_SCANTUPLE,"v_scanslot");...
  • 这里为每一个step创建了一个Block。
    在这里插入图片描述
	opblocks = palloc(sizeof(LLVMBasicBlockRef) * state->steps_len);for (int opno = 0; opno < state->steps_len; opno++)opblocks[opno] = l_bb_append_v(eval_fn, "b.op.%d.start", opno);
  • 将builder位置调整到第一个block中,开始build。
	LLVMBuildBr(b, opblocks[0]);for (int opno = 0; opno < state->steps_len; opno++){...}LLVMDisposeBuilder(b);

2.2 EEOP_SCAN_FETCHSOME计算

EEOP_SCAN_FETCHSOME原函数

非JIT表达式计算EEOP_SCAN_FETCHSOME流程:

  1. 从econtext中拿到tts赋给scanslot。
  2. 走EEOP_SCAN_FETCHSOME分支计算econtext。
ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)TupleTableSlot *scanslot;...scanslot = econtext->ecxt_scantuple;...EEO_SWITCH(){...EEO_CASE(EEOP_SCAN_FETCHSOME){...slot_getsomeattrs(scanslot, op->d.fetch.last_var);EEO_NEXT();}...}
EEOP_SCAN_FETCHSOME IR构造

JIT表达式计算EEOP_SCAN_FETCHSOME流程:

	/** l_load_struct_gep = * * LLVMBuildLoad(b,*               LLVMStructGetTypeAtIndex(StructExprContext, 1),*               LLVMBuildStructGEP(b, StructExprContext, v_econtext, 1, "")*               "v_scanslot")*/v_scanslot = l_load_struct_gep(b,StructExprContext,v_econtext,FIELDNO_EXPRCONTEXT_SCANTUPLE,"v_scanslot");...case EEOP_SCAN_FETCHSOME:{TupleDesc	desc = NULL;LLVMValueRef v_slot;LLVMBasicBlockRef b_fetch;LLVMValueRef v_nvalid;LLVMValueRef l_jit_deform = NULL;const TupleTableSlotOps *tts_ops = NULL;
  • 前面已经为每一个case都创建了一个BasicBlock。
  • l_bb_before_v在当前switch的BasicBlock前增加了一个新的Block。
  • 新的Block的语义:
    • if (v_nvalid >= op->d.fetch.last_var) // 跳转到下一个case的Block:opblocks[opno + 1]
    • else // 继续执行 当前Block 中的代码
		b_fetch = l_bb_before_v(opblocks[opno + 1],"op.%d.fetch", opno);v_slot = v_scanslot;v_nvalid =l_load_struct_gep(b,StructTupleTableSlot,v_slot,FIELDNO_TUPLETABLESLOT_NVALID,"");LLVMBuildCondBr(b,LLVMBuildICmp(b, LLVMIntUGE, v_nvalid,l_int16_const(lc, op->d.fetch.last_var),""),opblocks[opno + 1], b_fetch);
  • 将builder的插入点调整到b_fetch块的末尾,继续在b_fetch中增加代码:
		LLVMPositionBuilderAtEnd(b, b_fetch);{LLVMValueRef params[2];params[0] = v_slot;params[1] = l_int32_const(lc, op->d.fetch.last_var);
  • 创建一个调用指令,等价与slot_getsomeattrs(scanslot, op->d.fetch.last_var);
/** API调用:* LLVMBuildCall2(*   b, *   LLVMGetFunctionType(LLVMGetNamedFunction(llvm_types_module, "slot_getsomeattrs_int")), *   LLVMAddFunction(mod, "slot_getsomeattrs_int", LLVMGetFunctionType(LLVMGetNamedFunction(llvm_types_module, "slot_getsomeattrs_int"))), *   params, *   2,*   "");*/l_call(b,llvm_pg_var_func_type("slot_getsomeattrs_int"),llvm_pg_func(mod, "slot_getsomeattrs_int"),params, lengthof(params), "");}
  • 继续到下一个Block执行。
		LLVMBuildBr(b, opblocks[opno + 1]);break;}

2.3 EEOP_SCAN_VAR计算

在这里插入图片描述

2.4 EEOP_FUNCEXPR_STRICT计算

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/14325.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java设计模式 _行为型模式_迭代器模式

一、迭代器模式 1、迭代器模式 迭代器模式&#xff08;Iterator Pattern&#xff09;是一种行为型设计模式&#xff0c;用于顺序访问集合对象的元素&#xff0c;不需要关心集合对象的底层表示。如&#xff1a;java中的Iterator接口就是这个工作原理。 2、实现思路 &#xff0…

tomcat jdbc连接池的默认配置配置方案

MySQL 5.0 以后针对超长时间数据库连接做了一个处理&#xff0c;即一个数据库连接在无任何操作情况下过了 8 个小时后(MySQL 服务器默认的超时时间是 8 小时)&#xff0c;MySQL 会自动把这个连接关闭。在数据库连接池中的 connections 如果空闲超过 8 小时&#xff0c;MySQL 将…

国家自然博物馆“云端自然”线上虚拟展厅是如何搭建的?

国家级综合性自然博物馆国家自然博物馆&#xff0c;联手积木易搭打造“云端自然”线上虚拟展览&#xff0c;形成一个集参观游览、科普教育为一体的线上虚拟数字博物馆平台&#xff0c;让数千以至数万年的古生物&#xff0c;栩栩如生地呈现在我们面前。 通过数字化的展示手段&am…

在做题中学习(61):连续数组

525. 连续数组 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;前缀和 哈希表 转化&#xff1a;将 0 ——> -1 转变为&#xff1a;找到和为0的最长子数组 细节&#xff1a; 1.哈希表存什么 前缀和 &#xff0c; 长度 2.什么时候存入哈希表 先处理前一个&…

怎么用二维码看excel表格?生成文件二维码的制作技巧

Excel表格怎么放到二维码中&#xff0c;让其他人通过扫码查看数据呢&#xff1f;现在文件放入二维码中展示在很多的场景中都有应用&#xff0c;比如通知、数据、作品、报告等类型的内容都可以通过扫码的方式在手机上展现&#xff0c;那么如何将文件生成二维码呢&#xff1f; 文…

DVWA登录页面空白问题解决

问题&#xff1a; 创建完成后打开登录页面&#xff0c;发现打不开&#xff0c;一片空白 解决&#xff1a; php版本不对&#xff0c;更换版本即可

Rust Tarui 中的 Scrcpy 客户端,旨在提供控制安卓设备的鼠标和按键映射,类似于游戏模拟器。

Scrcpy-mask 为了实现电脑控制安卓设备&#xff0c;本人使用 Tarui Vue 3 Rust 开发了一款跨平台桌面客户端。该客户端能够提供可视化的鼠标和键盘按键映射配置。通过按键映射实现了实现类似安卓模拟器的多点触控操作&#xff0c;具有毫秒级响应速度。该工具可广泛用于电脑控…

YashanDB与慧点科技完成兼容互认证

近日&#xff0c;深圳计算科学研究院崖山数据库系统YashanDB与慧点科技顺利完成兼容性互认证。经严格测试&#xff0c;双方产品完全兼容&#xff0c;稳定运行&#xff0c;共同支撑政府、企业、金融等办公应用场景下的数字化转型升级&#xff0c;为企业的信息技术应用创新提供坚…

石油化工巡检机器人:应对挑战的创新力量

在石油化工领域&#xff0c;安全始终是高悬的达摩克利斯之剑。人工巡检面临诸多痛点&#xff0c;如高危环境对人身安全的巨大威胁&#xff0c;复杂工况下难以做到全面细致监测&#xff0c;对有害气体检测存在滞后性&#xff0c;还有恶劣天气对巡检工作的严重干扰。而这些痛点&a…

绕过最新版bilibili app反frida机制

问题说明 截止到2024年5月1日&#xff0c;B站最新版的安卓APP&#xff08;7.76.0&#xff09;有反Frida机制&#xff0c;不管是spawn还是attach&#xff0c;都无法注入frida&#xff0c;如下图所示。本文介绍一下如何绕过它 方法 定位检测点 检测Frida的机制一般在Native层实…

GS5812G 21V、2A同步降压DC/DC转换器芯片IC

一般描述 该GS5812G是一个同步降压DC/DC转换器与快速恒定的时间(FCOT)模式控制。该器件提供4. 5V至21V的输入电压范围和2A连续负载电流能力。它是恒定时间脉宽调制(PWM)控制器&#xff0c;支持 FCOT模式控制。工作频率取决于输入和输出电压条件。 该GS5812G故障…

ARM-2

c语言实现三盏灯的控制 #ifndef __LED_H__ #define __LED_H__typedef struct {volatile unsigned int MODER;volatile unsigned int OTYPER;volatile unsigned int OSPEEDER;volatile unsigned int PUPDR;volatile unsigned int IDR;volatile unsigned int ODR;volatile unsig…

顶顶通实时质检系统-黑名单拦截功能配置流程

文章目录 前言联系我们配置流程一、黑名单导入二、白名单导入三、外部黑名单四、靓号规则五、创建拦截规则六、拦截条件七、功能配置 拦截记录与统计拦截记录拦截统计 前言 上篇文章讲解了顶顶通实时质检系统黑名单的功能介绍&#xff0c;本篇文章主要讲解顶顶通黑名单拦截功能…

docker 安装 SonarQube

文章目录 docker 安装 SonarQube一、修改句柄二、创建挂载文件夹三、拉取镜像四、修改 PG 库4.1、创建用户4.2、创建库 五、启动和挂载六、访问七、安装插件 docker 安装 SonarQube 版本&#xff1a;8.9 对 JDK 8 最大支持为 8.9 版本 一、修改句柄 #修改文件句柄数量&#…

智能合约语言(eDSL)—— 并行化方案 2

这个并行算法最初其实是在aptos上实现的&#xff0c;aptos上使用的是move虚拟机&#xff0c;后来我把它移植到我们链上了&#xff0c;但是wasm虚拟机。还是费了不少事情。 目前evm并行也比较火&#xff0c;像monad&#xff0c;sei等。经过调研发现&#xff0c;其实evm的并行&am…

Discourse 使用 DiscourseConnect 调用接口 admin/users/sync_sso 404 错误

在对用户数据通过 SSO 同步的时候&#xff0c;调用提示 404 错误。 我们使用的是 Java 的代码。 2024-05-23_16-34-421340802 70.3 KB 如上图&#xff0c;返回显示的代码为 404。 问题原因 出现上面错误的原因是安装的 Discourse 实例的 discourse connect 没有启用。 2024-…

【Unity2D:C#Script】实现角色射击功能

一、创建子弹预制体 1. 创建子弹预制体 2. 调整图片大小、层级 二、为子弹添加碰撞体积 1. 添加Box Collider 2D、Rigidbody 2D组件 2. 锁定z轴 三、编辑敌人脚本 注&#xff1a;在以下代码中&#xff0c;只显示本章节新增的代码&#xff0c;省略原有的代码 1. 为敌人添加生…

力扣刷题---返回word中所有不重复的单词

当需要从一个数据集合中去除重复元素时&#xff0c;set是一个很好的选择。由于其不允许存储重复的元素&#xff0c;因此可以很容易地实现去重功能。这在处理原始数据或进行数据分析时特别有用。 题目&#xff1a; 给定一个字符串数组 words&#xff0c;请返回一个由 words 中所…

chrome125.0.6422.60驱动包下载

百度网盘地址:https://pan.baidu.com/s/1DAr_O58GQ6m4sk_QePZscA?pwd=5t0j 提取码:5t0j Chrome驱动包(ChromeDriver)是一个用于支持自动化测试的工具,它提供了对Google Chrome浏览器的控制,使您可以编写和运行自动化脚本来测试网站。这个驱动程序是由Selenium项目开…

国内大模型价格战全面爆发:新旧势力逐鹿江湖【附主流模型价格对比】

近年来&#xff0c;随着人工智能技术的不断发展&#xff0c;大模型逐渐成为行业的焦点。然而&#xff0c;伴随而来的却是一场价格战。DeepSeek率先推出超低价服务&#xff0c;随后字节跳动、阿里巴巴、百度、科大讯飞、腾讯等巨头纷纷跟进&#xff0c;使得这一领域的竞争愈演愈…