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

news2024/12/24 9:31:11

相关
《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.coloradmin.cn/o/1687408.html

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

相关文章

计算机系统基础 8 循环程序

概要 两种实现方法——分支指令实现和专门的循环语句实现以及有关循环的优化。 分支指令实现 倒计数 …… MOV ECX&#xff0c;循环次数 LOOPA&#xff1a;…… …… DEC ECX JNE LOOPA 正计数 …… MOV ECX&#xff0c;0 LOOPA&#xff1a; …… INC ECX CMP …

Kafka-集群管理者(Controller)选举机制、任期(epoch)机制

Kafka概述 Kafka-集群管理者&#xff08;Controller&#xff09;选举机制 Kafka中的Controller是Kafka集群中的一个特殊角色&#xff0c;负责对整个集群进行管理和协调。Controller的主要职责包括分区分配、副本管理、Leader选举等。当当前的Controller节点失效或需要进行重新…

【CALayer-CALayer的transform属性 Objective-C语言】

一、接下来,我们来说的是这个,transform的属性 1.layer的transform属性, 把最后一份代码command + C、command + V、一份儿,改个名字, Name:04-CALayer的transform属性, 我们把这个代码稍微修改一下, 我们先添加了一个layer,到控制器的view上, 然后呢,这两句话不…

【安装笔记-20240524-Windows-安装测试 7-Zip】

安装笔记-系列文章目录 安装笔记-20240524-Windows-安装测试 7-Zip 文章目录 安装笔记-系列文章目录安装笔记-20240524-Windows-安装测试 7-Zip 前言一、软件介绍名称&#xff1a;7-Zip主页官方介绍7-Zip 主要特征 二、安装步骤测试版本&#xff1a;24.05 (2024-05-14) for Wi…

保安维稳,四信以科技构筑高速公路安全智慧防线

近日&#xff0c;广东梅大高速发生严重塌方事故&#xff0c;造成了严重的人员伤亡和财产损失。这一事件在公众心中敲响了安全的警钟&#xff0c;再次引起了公众对于交通设施运营安全性的重点关注。 国务院安委会办公室和国家防灾减灾救灾委员会办公室等主管机构先后印发紧急通知…

CentOS7 部署单机版 elasticsearch

一、环境准备 1、准备一台系统为CentOS7的服务器 [rootlocalhost ~]# cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core) 2、创建新用户&#xff0c;用于elasticsearch服务 # elastic不允许使用root账号启动服务 [rootlocalhost ~]# useradd elastic [rootlo…

58同城如何降低 80%的机器成本 | OceanBase案例

本文作者&#xff1a;58同城架构师刘春雷 一、背景介绍 58同城作为中国互联网生活服务领域的领军者&#xff0c;其平台规模居国内之首&#xff0c;涵盖了包括车辆交易、房产服务、人才招聘、本地生活服务以及金融等多元化的业务场景。 因其业务的广泛性和多样性&#xff0c;我…

自主创新助力科技强军,麒麟信安闪耀第九届军博会

由中国指挥与控制学会主办的中国指挥控制大会暨第九届北京军博会于5月17日-19日在北京国家会议中心盛大开展&#xff0c;政府、军队、武警、公安、交通、人防、航天、航空、兵器、船舶、电科集团等从事国防军工技术与产业领域的30000多名代表到场参加。 麒麟信安作为国产化方案…

【okhttp】小问题记录合集

can’t create native thread 问题描述 OkHttpClient 每次使用都new创建&#xff0c;造成OOM&#xff0c;提示can’t create native thread… 问题分析 没有将OkHttpClient单例化. 每个client对象都有自己的线程池和连接池&#xff0c;如果为每个请求都创建一个client对象&a…

Java设计模式 _行为型模式_中介者模式

一、中介者模式 1、中介者模式 中介者模式&#xff08;Mediator Pattern&#xff09;是一种行为型模式。主要通过一个中介类&#xff0c;该类通常处理不同类之间的通信&#xff0c;并支持松耦合&#xff0c;使代码易于维护。 2、实现思路 &#xff08;1&#xff09;、定义实体…

入门级指纹密码智能锁方案简析以及适用芯片SSD210介绍

上篇我们大概讲了一下门锁的发展历史&#xff0c;近几年家用智能门锁行业中近几年的市场增长变化&#xff0c;举例说明了智能猫眼门锁在类市场份额最大的产品的一些技术参数以及芯片功能框架。 智能猫眼锁核心解决方案以及适用的芯片推荐简介https://blog.csdn.net/Chipsupply…

基于Pytorch的卷积神经网络MNIST手写数字识别

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景 手写数字识别是计算机视觉和模式识别领域的一个经典问题。MNIST数据集是一个包含大量手写数字图片的…

军工单位如何做到安全跨网文件交换与导出的

在现代信息化战争中&#xff0c;军工单位在信息安全方面的需求尤为突出。跨网文件交换与导出作为军工单位日常运营的重要环节&#xff0c;面临着网络带宽限制、数据安全风险、合规性要求和传输稳定性等挑战。下面&#xff0c;我们将从以下几个方面探讨军工单位如何实现安全、高…

【UE5.1 多线程 异步】“Async Blueprints Extension”插件使用记录

目录 一、异步生成Actor示例 二、异步计算示例 参考视频 首先需要在商城中下载“Async Blueprints Extension”插件 一、异步生成Actor示例 2. 创建一个线程类&#xff0c;这里要指定父类为“LongAsyncTask”、“InfiniteAsyncTask”、“ShortAsyncTask”中的一个 在线程类…

成品短视频APP源码搭建

在数字化时代&#xff0c;短视频已成为全球范围内的流行趋势&#xff0c;吸引了大量的用户和内容创作者。对于有志于进入短视频领域的企业和个人来说&#xff0c;成品短视频APP源码搭建提供了一条快速、高效的路径。本文将探讨成品短视频APP源码搭建的过程及其优势&#xff0c;…

sheng的学习笔记-docker部署Greenplum

目录 docker安装gp数据库 mac版本 搭建gp数据库 连接数据库 windows版本 搭建gp数据库 连接数据库 docker安装gp数据库 mac版本 搭建gp数据库 打开终端&#xff0c;输入代码&#xff0c;查看版本 ocker search greenplum docker pull projectairws/greenplum docker…

uni-starter创建App项目最全流程(日后还有其他功能会不断更新)

一、创建项目 在HbuilderX中点击创建项目&#xff0c;选择uni-starter模板&#xff0c;选择阿里云、Vue3&#xff0c;填写项目名称后点击创建。如果没有下载过uni-starter会自动下载该插件&#xff0c;如下图&#xff1a; 二、 创建云服务器并关联项目 如果是第一次使用&#…

Linux中gcc/g++的基本使用

目录 gcc/g的使用gcc/g是如何生成可执行文件的预处理编译汇编链接 库.o文件是如何与库链接的&#xff1f; debug版本和release版本 gcc/g的使用 在windows中&#xff0c;我们在VS中编写好了代码之后就可以直接在VS中对源码进行编译等操作后运行 而在Linux下&#xff0c;我们可…

C++_vector操作使用

文章目录 &#x1f680;1.1 vector介绍&#x1f680;1.2 vector的初始化&#x1f680;1.3 vector的常用内置函数&#x1f680;1.4 vector的遍历 &#x1f680;1.1 vector介绍 vector是表示可变大小数组的序列容器。就像数组一样&#xff0c;vector也采用的连续存储空间来存储元…

领域知识 | 智能驾驶安全领域部分常见概论

Hi&#xff0c;早。 最近想买个新能源车&#xff0c;这个车吧相比于之前的内燃车&#xff0c;新能源车与外界的交互多了很多。比如娱乐的第三方应用&#xff0c;OTA升级等应用。 交互带来的便利越多&#xff0c;暴露的风险自然也就越大&#xff0c;相比于手机等消费者终端设备…