Postgresql中JIT函数能否inline的依据function_inlinable

相关
《LLVM的ThinLTO编译优化技术在Postgresql中的应用》

在JIT inline函数的过程中,会通过函数的bc代码,经过一系列规则、成本的判断来决定函数能否Inline,本篇重点分析这段逻辑:function_inlinable。

总结速查:

  • 入参F(llvm::Function):待inline函数
  • 入参functionStates(数组):记录了表达式计算所需要的所有函数,在function_inlinable函数内部检查的过程中,函数调用的其他函数,能inline的也会被加到这个数组中。
  • 入参worklist(数组):记录了待处理的{函数名,搜索路径},包括本次表达式计算的函数 和 在function_inlinable函数内部检查的过程中,函数调用的其他函数。
  • 入参visitedFunctions(llvm::Function的SET):处理过的函数名。
  • 入参running_instcount:经过function_inlinable的dfs搜索,包括当前函数和所有被调用者的指令数的总和。
  • 入参importVars(String SET ):全局变量 和 当前函数调用的其他函数的函数名,类似于符号表。

function_inlinable会做dfs搜索所有调用到的函数,关心函数的指令数、里面用到的全局变量的个数。

1 function_inlinable part 1

function_inlinable(...)
{...
  • 弱定义函数,__attribute__((weak)),不会Inline。
	if (F.isInterposable())return false;
  • 通常指的是C代码中有inline关键字的函数,不需要这里再inline了。
	if (F.hasAvailableExternallyLinkage())return false;
  • 把函数从IR文件加载到内存中使用。
	if (F.materialize())elog(FATAL, "failed to materialize metadata");
  • 确定函数没有NoInline属性(后文有个例子)。
	if (F.getAttributes().hasFnAttr(llvm::Attribute::NoInline)){ilog(DEBUG1, "ineligibile to import %s due to noinline",F.getName().data());return false;}
  • function_references目的是为了了解当前函数引用了哪些变量和其他函数,评估它的大致复杂度。
  • 这里以 dexp函数为例展开讲下function_references的流程:
	function_references(F, running_instcount, referencedVars, referencedFunctions);

2 function_references

2.1 基础知识

  • BasicBlock 表示的是基本块类,Arugument 表示的是函数的形参,Constant 表示的是形如 i32 4 的常量,Instruction 表示的是形如 add i32 %a,%b 的指令。
  • Value 是一个非常基础的基类,一个继承于 Value 的子类表示它的结果可以被其他地方使用。
  • User代表了任何可以拥有操作数的LLVM对象。例如%1 = add i32 %a, %b是Instruction,同时也是一个User,抽象理解就是拥有操作数的一切对象都是User。
    请添加图片描述

2.2 dexp的ir

定义:

; Function Attrs: nounwind uwtable
define dso_local i64 @dexp(ptr nocapture noundef readonly %0) local_unnamed_addr #6 {%2 = getelementptr inbounds %struct.FunctionCallInfoBaseData, ptr %0, i64 0, i32 6, i64 0, i32 0%3 = bitcast ptr %2 to ptr%4 = load double, ptr %3, align 8%5 = fcmp uno double %4, 0.000000e+00br i1 %5, label %28, label %66:                                                ; preds = %1%7 = tail call double @llvm.fabs.f64(double %4) #22%8 = fcmp oeq double %7, 0x7FF0000000000000br i1 %8, label %9, label %129:                                                ; preds = %6%10 = fcmp ogt double %4, 0.000000e+00%11 = select i1 %10, double %4, double 0.000000e+00br label %2812:                                               ; preds = %6%13 = tail call ptr @__errno_location() #23store i32 0, ptr %13, align 4%14 = tail call double @exp(double noundef %4) #20%15 = load i32, ptr %13, align 4%16 = icmp eq i32 %15, 34br i1 %16, label %17, label %21, !prof !1117:                                               ; preds = %12%18 = fcmp une double %14, 0.000000e+00br i1 %18, label %19, label %2019:                                               ; preds = %17tail call void @float_overflow_error() #24unreachable20:                                               ; preds = %17tail call void @float_underflow_error() #24unreachable21:                                               ; preds = %12%22 = tail call double @llvm.fabs.f64(double %14) #22%23 = fcmp oeq double %22, 0x7FF0000000000000br i1 %23, label %24, label %25, !prof !1124:                                               ; preds = %21tail call void @float_overflow_error() #24unreachable25:                                               ; preds = %21%26 = fcmp oeq double %14, 0.000000e+00br i1 %26, label %27, label %28, !prof !1127:                                               ; preds = %25tail call void @float_underflow_error() #24unreachable28:                                               ; preds = %25, %9, %1%29 = phi double [ %11, %9 ], [ %14, %25 ], [ %4, %1 ]%30 = bitcast double %29 to i64ret i64 %30
}

2.3 function_references函数

static void
function_references(llvm::Function &F,int &running_instcount,llvm::SmallPtrSet<llvm::GlobalVariable *, 8> &referencedVars,llvm::SmallPtrSet<llvm::Function *, 8> &referencedFunctions)
{
  • 申请32个位置的Set存放User指针,具体就是Instruction
	llvm::SmallPtrSet<const llvm::User *, 32> Visited;for (llvm::BasicBlock &BB : F){for (llvm::Instruction &I : BB){if (llvm::isa<llvm::DbgInfoIntrinsic>(I))continue;
  • 申请8个位置的vector存放llvm::User指针(Instruction的基类):
			llvm::SmallVector<llvm::User *, 8> Worklist;Worklist.push_back(&I);
  • 指令计数running_instcount(Instruction的基类):
			running_instcount++;while (!Worklist.empty()) {llvm::User *U = Worklist.pop_back_val();
  • 这条指令之前有没有被记录过:
				if (!Visited.insert(U).second)continue;
  • 遍历Instruction的操作数operands,操作数的基类也是User:
				for (auto &OI : U->operands()) {llvm::User *Operand = llvm::dyn_cast<llvm::User>(OI);if (!Operand)continue;
  • 当前拿到的操作数是一个baseblock的地址,一般是用于跳转,不需要记录:
					if (llvm::isa<llvm::BlockAddress>(Operand))continue;
  • 这里看到一个全局变量,需要记录到referencedVars中,并把全局变量的定义拿出来,放到Worklist里面去统计一把,比如一个全局变量定义为int a = 1,那么这一个Instruction会在下一轮循环中被统计。
					if (auto *GV = llvm::dyn_cast<llvm::GlobalVariable>(Operand)) {referencedVars.insert(GV);if (GV->hasInitializer())Worklist.push_back(GV->getInitializer());continue;}
  • 这里发现一个操作数是另一个函数,说明有其他函数引用,将Function指针记录到referencedFunctions中。
					if (auto *CF = llvm::dyn_cast<llvm::Function>(Operand)) {referencedFunctions.insert(CF);continue;}Worklist.push_back(Operand);}}}}
}

执行结束后:

  • running_instcount:35
    • IR中有35个指令
  • referencedVars:空
  • referencedFunctions:5个函数

dexp函数的IR分两部分:函数摘要和函数定义(index文件就是收集了bc文件中的函数摘要)

摘要:

^62 = gv: (name: "dexp", summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 1, canAutoHide: 0), insts: 35, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 0, noUnwind: 1, mayThrow: 0, hasUnknownCall: 0, mustBeUnreachable: 0), calls: ((callee: ^302), (callee: ^157), (callee: ^277), (callee: ^54))))) ; guid = 3352526880228194314

定义

$ cat float.ll | grep -A 58 '@dexp'
define dso_local i64 @dexp(ptr nocapture noundef readonly %0) local_unnamed_addr #6 {%2 = getelementptr inbounds %struct.FunctionCallInfoBaseData, ptr %0, i64 0, i32 6, i64 0, i32 0%3 = bitcast ptr %2 to ptr%4 = load double, ptr %3, align 8%5 = fcmp uno double %4, 0.000000e+00br i1 %5, label %28, label %66:                                                ; preds = %1%7 = tail call double @llvm.fabs.f64(double %4) #22%8 = fcmp oeq double %7, 0x7FF0000000000000br i1 %8, label %9, label %129:                                                ; preds = %6%10 = fcmp ogt double %4, 0.000000e+00%11 = select i1 %10, double %4, double 0.000000e+00br label %2812:                                               ; preds = %6%13 = tail call ptr @__errno_location() #23store i32 0, ptr %13, align 4%14 = tail call double @exp(double noundef %4) #20%15 = load i32, ptr %13, align 4%16 = icmp eq i32 %15, 34br i1 %16, label %17, label %21, !prof !1117:                                               ; preds = %12%18 = fcmp une double %14, 0.000000e+00br i1 %18, label %19, label %2019:                                               ; preds = %17tail call void @float_overflow_error() #24unreachable20:                                               ; preds = %17tail call void @float_underflow_error() #24unreachable21:                                               ; preds = %12%22 = tail call double @llvm.fabs.f64(double %14) #22%23 = fcmp oeq double %22, 0x7FF0000000000000br i1 %23, label %24, label %25, !prof !1124:                                               ; preds = %21tail call void @float_overflow_error() #24unreachable25:                                               ; preds = %21%26 = fcmp oeq double %14, 0.000000e+00br i1 %26, label %27, label %28, !prof !1127:                                               ; preds = %25tail call void @float_underflow_error() #24unreachable28:                                               ; preds = %25, %9, %1%29 = phi double [ %11, %9 ], [ %14, %25 ], [ %4, %1 ]%30 = bitcast double %29 to i64ret i64 %30
}
  • 引用函数个数:去重后5个在这里插入图片描述
  • 指令个数:35
    在这里插入图片描述
  • 引用全局变量个数:0个

和function_references计算结果一致。

3 function_inlinable part 2

  • 记录全局变量到importVars,并增加成本:
	for (llvm::GlobalVariable* rv: referencedVars){...importVars.insert(rv->getName());/* small cost attributed to each cloned global */running_instcount += 5;}
  • 标记当前函数已经处理过了:
	visitedFunctions.insert(&F);
  • 检查dexp调用的函数:这里会处理5个函数:
    • llvm.fabs.f64
    • __errno_location
    • exp
    • float_overflow_error
    • float_underflow_error
	for (llvm::Function* referencedFunction: referencedFunctions){llvm::StringSet<> recImportVars;if (referencedFunction->materialize())elog(FATAL, "failed to materialize metadata");
  • 判断是不是llvm内建函数,例如循环给数组赋零有可能被clang在-O2时被优化为llvm.memset
  • dexp调用的五个函数中,只有llvm.fabs.f64是llvm内建函数:
		if (referencedFunction->isIntrinsic())continue;
  • 已经处理过了?
		if (!visitedFunctions.insert(referencedFunction).second)continue;
  • 当前函数在其他编译单元?
  • 例如__errno_location函数就在glibc中。
		if (referencedFunction->hasExternalLinkage()){llvm::StringRef funcName = referencedFunction->getName();/** Don't bother checking for inlining if remaining cost budget is* very small.*/
  • inline_initial_cost默认给150。
  • subThreshold = inline_initial_cost * inline_cost_decay_factor = 150 * 0.5 = 75
			if (subThreshold < 5)continue;auto it = functionStates.find(funcName);if (it == functionStates.end()){
  • 注意functionStates数组里面包含本次表达式计算用到的所有函数,比如int4abs、dexp、slot_getsomeattrs_int、i4tod等等。
  • 这里会把需要inline的函数加到functionStates中,先不做其他处理。
				FunctionInlineState inlineState;inlineState.costLimit = subThreshold;inlineState.processed = false;inlineState.inlined = false;inlineState.allowReconsidering = false;functionStates[funcName] = inlineState;worklist.push_back({funcName, searchpath});ilog(DEBUG1,"considering extern function %s at %d for inlining",funcName.data(), subThreshold);}...
  • 弱定义函数,__attribute__((weak)),排除。
		if (referencedFunction->isInterposable())return false;
  • 递归调用function_inlinable,检查内层函数。
		if (!function_inlinable(*referencedFunction,subThreshold,functionStates,worklist,searchpath,visitedFunctions,running_instcount,recImportVars)){return false;}/* import referenced function itself */importVars.insert(referencedFunction->getName());/* import referenced function and its dependents */for (auto& recImportVar : recImportVars)importVars.insert(recImportVar.first());}

经过function_inlinable的递归调用,dfs所有会调用到的函数,最终:

  • 需要inline的函数已经都加入到functionStates中。
  • 需要Inline的{函数名字,搜索路径}在worklist中。
  • 函数名和全局变量名,全部加入到worklist。

返回true表示当前函数可以inline。

	return true;
}

4 其他

dexp

怎么拿到函数的guid:funcGUID = llvm::GlobalValue::getGUID(cfuncname);
(GUID是用函数名MD5 hash出来的)
funcGUID = 3352526880228194314

index文件中查看函数属性:

^12463 = gv: (guid: 3352526880228194314, summaries: (function: (module: ^604, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 1, canAutoHide: 0), insts: 79, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 1, alwaysInline: 0, noUnwind: 1, mayThrow: 0, hasUnknownCall: 0, mustBeUnreachable: 0), calls: ((callee: ^6190), (callee: ^59633), (callee: ^10786), (callee: ^32543)))))

这里函数被标记了noInline: 1,所以该函数不会被inline。

但是dexp为什么不能被inline呢?看起来函数不长,分支也不多,也没有标记__attribute__((noinline))

Datum
dexp(PG_FUNCTION_ARGS)
{float8		arg1 = PG_GETARG_FLOAT8(0);float8		result;if (isnan(arg1))result = arg1;else if (isinf(arg1)){/* Per POSIX, exp(-Inf) is 0 */result = (arg1 > 0.0) ? arg1 : 0;}else{errno = 0;result = exp(arg1);if (unlikely(errno == ERANGE)){if (result != 0.0)float_overflow_error();elsefloat_underflow_error();}else if (unlikely(isinf(result)))float_overflow_error();else if (unlikely(result == 0.0))float_underflow_error();}PG_RETURN_FLOAT8(result);
}

原因是这里llvm是按O2编译的,按O0编译后noInline: 0

^10363 = gv: (guid: 3352526880228194314, summaries: (function: (module: ^604, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 1, canAutoHide: 0), insts: 35, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 0, noUnwind: 1, mayThrow: 0, hasUnknownCall: 0, mustBeUnreachable: 0), calls: ((callee: ^49065), (callee: ^8990)))))

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

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

相关文章

【python】常见的python下载库镜像源

python中的第三方库大多由国外提供&#xff0c;在国内直接进行下载时&#xff0c;可能会因为访问国外网络较慢&#xff0c;而出现下载超时的报错提醒&#xff0c;为了避免出现类似问题&#xff0c;我们可以在下载库时加入国内的镜像源来下载&#xff0c;这样就不会出现网络较慢…

分布式链路追踪工具Sky walking详解

1&#xff0c;为什么要使用分布式链路追踪工具 随着分布式系统和微服务架构的出现&#xff0c;且伴随着用户量的增加&#xff0c;项目的体量变得十分庞大&#xff0c;一次用户请求会经过多个系统&#xff0c;不同服务之间调用关系十分复杂&#xff0c;一旦一个系统出现错误都可…

Clickhouse中物化视图和位图和索引的用法

目录 聚合函数表引擎AggregatingMergeTree物化视图位图Clickhouse实现数据的有限更新索引 聚合函数 例如 max(val) , argMax(arg,val) 如果在聚合函数后面加入后缀if,则是 maxIf(val,UInt8) argMaxIf(arg,val,UInt8) 也就是当满足某个条件时候&#xff0c;才会对这一行数据进行…

iphone忘记锁屏密码怎么解锁?这些解锁方法你必须知道!

在使用iPhone的过程中经常会遇到很多问题&#xff0c;比如忘记了iPhone的锁屏密码。面对这样的情况&#xff0c;许多用户可能会感到手足无措。别担心&#xff0c;本文将为您详细介绍iPhone忘记锁屏密码的解锁方法&#xff0c;让您轻松解决这一烦恼。 一、使用iTunes备份恢复 如…

以gitee为例的git入门使用指北

安装git 在linux中我们首先需要使用 sudo apt install git来下载git 在windows中可以下载msysGit 链接&#xff1a;https://git-scm.com/download/win gitee准备 申请账号 建立仓库 ​ 点击新建仓库 这里一般是私有库&#xff0c;点击创建&#xff0c;这时你就拥有一个线上…

python作业五

题目&#xff1a;注册登录 制作一个注册登录模块 注册&#xff1a;将用户填入的账户和密码保存到一个文件(users.bin) 登陆&#xff1a;将用户填入账户密码和users.bin中保存的账户密码进行比对,如果账户和密码完全相同 那 么登录成功&#xff0c;否则登录失败…

电脑屏幕监控软件有哪些?8款受欢迎的电脑屏幕监控软件

电脑屏幕监控软件有哪些&#xff1f;8款受欢迎的电脑屏幕监控软件 市场上有很多监控软件&#xff0c;因为太多&#xff0c;很多老板不知道怎么选&#xff0c;今天小编从它们各自的特点、优势、未来发展趋势几方面&#xff0c;介绍8款受欢迎的电脑屏幕监控软件。 第一是&#x…

探索网站支付系统的奥秘,从Vue3和Spring Boot开始(入门级项目实战+在线教程)附赠项目源码!

你是否曾经在购物时&#xff0c;对着电脑屏幕前的“支付成功”四个字感到好奇&#xff1f;这背后的秘密究竟是什么&#xff1f; 今天&#xff0c;让我们一起揭开支付系统的神秘面纱&#xff0c;探索其背后的技术实现。 在这个基于Vue3和Spring Boot的支付项目实战中&#xff…

Java实名认证API、婚恋网实名认证

中国网络婚恋交友行业发展近20年&#xff0c;电脑端网络婚恋服务已经较为成熟&#xff0c;商业模式也较为完善。但随着移动互联网的快速发展&#xff0c;移动端成为婚恋交友企业核心用户新的来源渠道。网络婚恋交友移动端人群覆盖规模逐渐超过电脑端人群&#xff0c;标志着以移…

【Golang】实现 Excel 文件下载功能

在当今的网络应用开发中&#xff0c;提供数据导出功能是一项常见的需求。Excel 作为一种广泛使用的电子表格格式&#xff0c;通常是数据导出的首选格式之一。在本教程中&#xff0c;我们将学习如何使用 Go 语言和 Gin Web 框架来创建一个 Excel 文件&#xff0c;并允许用户通过…

MySQL学习笔记10——日志

日志 一、日志1、通用查询日志&#xff08;1&#xff09;开启通用查询日志&#xff08;2&#xff09;查看通用查询日志&#xff08;3&#xff09;删除通用查询日志 2、慢查询日志3、错误日志4、二进制日志&#xff08;1&#xff09;查看二进制日志&#xff08;2&#xff09;刷新…

Ansible-Playbook——部署LNMP架构

目录 环境准备 一、配置Nginx的Roles角色 1.创建Nginx所需的文件夹 2.编写Nginx配置文件 3.编写Nginx安装文件 4.编写Nginx控制器文件 5.编写Nginx任务文件 6.编写Nginx变量文件 二、配置Mysql的Roles角色 1.创建Mysql所需的文件夹 2.编写Mysql启动文件 3.编写Mysql…

源码部署与SaaS账号:企业软件选择的自建房与租赁公寓之辩

在数字化运营的时代&#xff0c;企业选择软件解决方案就如同在选择住所&#xff1a;源码部署类似于“自建房屋”&#xff0c;而SaaS账号则更像是“租赁公寓”。 自建房屋&#xff08;源码部署&#xff09; 当你选择自建房屋时&#xff0c;你需要投入大量的时间和资金来购买土地…

二叉树遍历总结

7.二叉树 二叉树理论基础 二叉树的种类 在我们解题过程中二叉树有两种主要的形式&#xff1a;满二叉树和完全二叉树。 满二叉树 完全二叉树 二叉搜索树 平衡二叉搜索树 C中map、set、multimap&#xff0c;multiset的底层实现都是平衡二叉搜索树&#xff0c;所以map、set的增…

Ubuntu服务器如何安装桌面

更新软件库 apt-get update 升级软件 apt-get upgrade 安装ubuntu桌面系统 apt-get install ubuntu-desktop 运行过程需要手动确认两次&#xff0c;选择 Y。 安装完成之后&#xff0c;终端输入 reboot&#xff0c;重启服务器。

实现左上角的固定视口但是网格以图片中心放大缩小

仅仅修改了showbk&#xff08;&#xff09; 函数部分&#xff0c;增加bkv4 直接采样&#xff0c;然后粘贴到左上角&#xff0c;实现多余部分裁剪&#xff0c;形成视口内放大缩小 // 程序&#xff1a;2D RPG 地图编辑器与摄像机追随 // 作者&#xff1a;bilibili 民用级脑的研发…

Scala网络爬虫实战:抓取QQ音乐的音频资源

引言 在当今数字化时代&#xff0c;互联网中蕴藏着海量的数据&#xff0c;而网络爬虫技术则是获取这些数据的重要工具之一。而Scala作为一种功能强大的多范式编程语言&#xff0c;结合了面向对象和函数式编程的特性&#xff0c;为网络爬虫开发提供了更多的可能性。在本文中&am…

C语言(指针)1

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸各位能阅读我的文章&#xff0c;诚请评论指点&#xff0c;关注收藏&#xff0c;欢迎欢迎~~ &#x1f4a5;个人主页&#xff1a;小羊在奋斗 &#x1f4a5;所属专栏&#xff1a;C语言 本系列文章为个人学习笔记&#x…

【操作系统】内存管理——地址空间连续内存分配与非连续内存分配

内存管理——地址空间&连续内存分配与非连续内存分配 一、地址空间1.1 计算机存储层次1.2 地址和地址空间1.3 虚拟存储的作用 二、内存分配2.1 静态内存分配2.2 动态内存分配 三、连续内存分配3.1 动态分区分配3.2 伙伴系统&#xff08;Buddy System&#xff09; 四、非连续…

深入浅出(五)JsonCpp库

JsonCpp库 1. JsonCpp 库1.1 JsonCpp库下载 2. JsonCpp库编译与部署3. C示例 1. JsonCpp 库 JsonCpp 是一个开源的 C 库&#xff0c;用于解析、生成和操作 JSON 数据。它提供了简单易用的 API&#xff0c;使得在 C 程序中处理 JSON 数据变得方便和高效。以下是 JsonCpp 库的一…