Postgresql源码(141)JIT系列分析汇总

news2025/1/22 14:37:02

JIT的东西比较零散,本篇对之前的一些列分析做个汇总、整理。
涉及:
《Postgresql源码(113)表达式JIT计算简单分析》
《Postgresql源码(127)投影ExecProject的表达式执行分析》
《Postgresql源码(128)深入分析JIT中的函数内联llvm_inline》
《LLVM(1)Fibonacci实例》
《LLVM(5)ORC实例分析》
《LLVM的ThinLTO编译优化技术在Postgresql中的应用》
《Postgresql中JIT函数能否inline的依据function_inlinable》

0 前言

从JIT的编译、加载,到使用方法、原理,整体对PG 引入JIT的细节做一些总结。

1 c/c++混合编译

PG编译时配置 --with-llvm可整合llvm到PG编译过程中。

./configure --prefix=/data/mingjie/pgroot99/pghome --enable-tap-tests --with-tcl \
 --enable-depend--enable-cassert --enable-debug --with-perl --with-openssl --with-libxml \
 --with-llvm \
 CFLAGS="-ggdb3 -O0 -g3 -fno-omit-frame-pointer" \
 CXXFLAGS="-ggdb3 -O0 -g3 -fno-omit-frame-pointer"

涉及JIT的几个源文件

[mingjie@pgev01 ~/pgroot99/pgsrc/src/backend/jit][PGROOT99:9901]$ tree
.
├── jit.bc
├── jit.c
├── jit.o
├── llvm
│   ├── llvmjit.c                -- C编译器
│   ├── llvmjit_deform.c         -- C编译器
│   ├── llvmjit_error.cpp        -- C++编译器
│   ├── llvmjit_expr.c           -- C编译器
│   ├── llvmjit_inline.cpp       -- C++编译器
│   ├── llvmjit_types.c          -- 后面单独讲
│   ├── llvmjit_wrap.cpp         -- C++编译器
│   ├── Makefile
├── Makefile
└── README

1.1 cc

  • gcc 编译的三个.o文件 llvmjit、llvmjit_deform、llvmjit_expr
  • 生成 llvmjit.o、llvmjit_deform.o、llvmjit_expr.c。
  • 注意 gcc -c 先不错 link,等后面一起做。
llvmjit.c        -> llvmjit.o
gcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -Wno-stringop-truncation -g -ggdb -O0 -g3 -gdwarf-2  -Wno-deprecated-declarations -fPIC -fvisibility=hidden -D__STDC_LIMIT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_CONSTANT_MACROS -D_GNU_SOURCE -DBUILD_EXAMPLES -I/data02/mingjie/bin/llvm15/include  -I../../../../src/include  -D_GNU_SOURCE -I/usr/include/libxml2 \
-c -o llvmjit.o llvmjit.c -MMD -MP -MF .deps/llvmjit.Po

llvmjit_deform.c -> llvmjit_deform.o
gcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -Wno-stringop-truncation -g -ggdb -O0 -g3 -gdwarf-2  -Wno-deprecated-declarations -fPIC -fvisibility=hidden -D__STDC_LIMIT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_CONSTANT_MACROS -D_GNU_SOURCE -DBUILD_EXAMPLES -I/data02/mingjie/bin/llvm15/include  -I../../../../src/include  -D_GNU_SOURCE -I/usr/include/libxml2 \
-c -o llvmjit_deform.o llvmjit_deform.c -MMD -MP -MF .deps/llvmjit_deform.Po

llvmjit_expr.c   -> llvmjit_expr.o
gcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -Wno-stringop-truncation -g -ggdb -O0 -g3 -gdwarf-2  -Wno-deprecated-declarations -fPIC -fvisibility=hidden -D__STDC_LIMIT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_CONSTANT_MACROS -D_GNU_SOURCE -DBUILD_EXAMPLES -I/data02/mingjie/bin/llvm15/include  -I../../../../src/include  -D_GNU_SOURCE -I/usr/include/libxml2 \
-c -o llvmjit_expr.o llvmjit_expr.c -MMD -MP -MF .deps/llvmjit_expr.Po

编译命令拼接:

  1. 第一段:gcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -Wno-stringop-truncation -g -ggdb -O0 -g3 -gdwarf-2 -Wno-deprecated-declarations -fPIC -fvisibility=hidden

  2. 第二段:llvm-config --cflags: -D__STDC_LIMIT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_CONSTANT_MACROS -D_GNU_SOURCE -DBUILD_EXAMPLES -I/data02/mingjie/bin/llvm15/include

  3. 第三段:-I…/…/…/…/src/include -D_GNU_SOURCE -I/usr/include/libxml2 -c -o llvmjit.o llvmjit.c -MMD -MP -MF .deps/llvmjit.Po

1.2 c++

  • g++ 编译的三个.o文件 llvmjit_error、llvmjit_inline、llvmjit_wrap
  • 生成 llvmjit_error.o、llvmjit_inline.o、llvmjit_wrap.o。
  • 也没有做 link。
llvmjit_error.cpp     ->   llvmjit_error.o
g++ -Wall -Wpointer-arith -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -fno-strict-aliasing -fwrapv -g -O2 -std=c++14 -fno-rtti -fPIC -fvisibility=hidden -fvisibility-inlines-hidden -D__STDC_LIMIT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_CONSTANT_MACROS -D_GNU_SOURCE -DBUILD_EXAMPLES -I/data02/mingjie/bin/llvm15/include  -I../../../../src/include  -D_GNU_SOURCE -I/usr/include/libxml2 \
-c -o llvmjit_error.o llvmjit_error.cpp -MMD -MP -MF .deps/llvmjit_error.Po

llvmjit_inline.cpp    ->   llvmjit_inline.o
g++ -Wall -Wpointer-arith -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -fno-strict-aliasing -fwrapv -g -O2 -std=c++14 -fno-rtti -fPIC -fvisibility=hidden -fvisibility-inlines-hidden -D__STDC_LIMIT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_CONSTANT_MACROS -D_GNU_SOURCE -DBUILD_EXAMPLES -I/data02/mingjie/bin/llvm15/include  -I../../../../src/include  -D_GNU_SOURCE -I/usr/include/libxml2 \
-c -o llvmjit_inline.o llvmjit_inline.cpp -MMD -MP -MF .deps/llvmjit_inline.Po

llvmjit_wrap.cpp      ->   llvmjit_wrap.o
g++ -Wall -Wpointer-arith -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -fno-strict-aliasing -fwrapv -g -O2 -std=c++14 -fno-rtti -fPIC -fvisibility=hidden -fvisibility-inlines-hidden -D__STDC_LIMIT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_CONSTANT_MACROS -D_GNU_SOURCE -DBUILD_EXAMPLES -I/data02/mingjie/bin/llvm15/include  -I../../../../src/include  -D_GNU_SOURCE -I/usr/include/libxml2 \
-c -o llvmjit_wrap.o llvmjit_wrap.cpp -MMD -MP -MF .deps/llvmjit_wrap.Po
  • g++ 六个文件汇总到 llvmjit.so
  • g++ 做 link
llvmjit.so

g++ -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -Wno-stringop-truncation \
-g -ggdb -O0 -g3 -gdwarf-2  -Wno-deprecated-declarations \
-fPIC -fvisibility=hidden \
-shared -o llvmjit.so  llvmjit.o llvmjit_error.o llvmjit_inline.o llvmjit_wrap.o llvmjit_deform.o llvmjit_expr.o \
-L../../../../src/port -L../../../../src/common  \
-L/data02/mingjie/bin/llvm15/lib \
-Wl,--as-needed -Wl,-rpath,'/data02/mingjie/pgroot99/pghome/lib',--enable-new-dtags  -fvisibility=hidden \
-lLLVMOrcJIT \
-lLLVMPasses \
-lLLVMObjCARCOpts \
-lLLVMCoroutines \
-lLLVMipo \
-lLLVMVectorize -lLLVMLinker -lLLVMIRReader -lLLVMAsmParser -lLLVMFrontendOpenMP -lLLVMJITLink -lLLVMX86TargetMCA -lLLVMMCA -lLLVMX86Disassembler -lLLVMX86AsmParser -lLLVMX86CodeGen -lLLVMCFGuard -lLLVMGlobalISel -lLLVMX86Desc -lLLVMX86Info -lLLVMMCDisassembler -lLLVMSelectionDAG -lLLVMInstrumentation -lLLVMAsmPrinter -lLLVMInterpreter -lLLVMExecutionEngine -lLLVMRuntimeDyld -lLLVMOrcTargetProcess -lLLVMOrcShared -lLLVMCodeGen -lLLVMTarget -lLLVMScalarOpts -lLLVMInstCombine -lLLVMAggressiveInstCombine -lLLVMTransformUtils -lLLVMBitWriter -lLLVMAnalysis -lLLVMProfileData -lLLVMSymbolize -lLLVMDebugInfoPDB -lLLVMDebugInfoMSF -lLLVMDebugInfoDWARF -lLLVMObject -lLLVMTextAPI -lLLVMMCParser -lLLVMMC -lLLVMDebugInfoCodeView -lLLVMBitReader -lLLVMCore -lLLVMRemarks -lLLVMBitstreamReader -lLLVMBinaryFormat -lLLVMSupport -lLLVMDemangle -lrt -ldl -lpthread -lm -lz -lzstd -ltinfo -lxml2

llvmjit_error编译命令拼接

  1. 第一段:g++ -Wall -Wpointer-arith -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -fno-strict-aliasing -fwrapv -g -O2 -std=c++14 -fno-rtti -fPIC -fvisibility=hidden -fvisibility-inlines-hidden

  2. 第二段: llvm-config --cflags: -D__STDC_LIMIT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_CONSTANT_MACROS -D_GNU_SOURCE -DBUILD_EXAMPLES -I/data02/mingjie/bin/llvm15/include

  3. 第三段:-I…/…/…/…/src/include -D_GNU_SOURCE -I/usr/include/libxml2 -c -o llvmjit_error.o llvmjit_error.cpp -MMD -MP -MF .deps/llvmjit_error.Po

llvmjit.so编译命令拼接

  1. 第一段:g++ -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -Wno-stringop-truncation -g -ggdb -O0 -g3 -gdwarf-2 -Wno-deprecated-declarations -fPIC -fvisibility=hidden -shared -o llvmjit.so llvmjit.o llvmjit_error.o llvmjit_inline.o llvmjit_wrap.o llvmjit_deform.o llvmjit_expr.o -L…/…/…/…/src/port -L…/…/…/…/src/common
  2. 第二段: llvm-config --ldflags: -L/data02/mingjie/bin/llvm15/lib
  3. 第三段:-Wl,–as-needed -Wl,-rpath,‘/data02/mingjie/pgroot99/pghome/lib’,–enable-new-dtags -fvisibility=hidden
  4. 第四段: llvm-config --libs: -lLLVMOrcJIT -lLLVMPasses -lLLVMObjCARCOpts -lLLVMCoroutines -lLLVMipo -lLLVMVectorize -lLLVMLinker -lLLVMIRReader -lLLVMAsmParser -lLLVMFrontendOpenMP -lLLVMJITLink -lLLVMX86TargetMCA -lLLVMMCA -lLLVMX86Disassembler -lLLVMX86AsmParser -lLLVMX86CodeGen -lLLVMCFGuard -lLLVMGlobalISel -lLLVMX86Desc -lLLVMX86Info -lLLVMMCDisassembler -lLLVMSelectionDAG -lLLVMInstrumentation -lLLVMAsmPrinter -lLLVMInterpreter -lLLVMExecutionEngine -lLLVMRuntimeDyld -lLLVMOrcTargetProcess -lLLVMOrcShared -lLLVMCodeGen -lLLVMTarget -lLLVMScalarOpts -lLLVMInstCombine -lLLVMAggressiveInstCombine -lLLVMTransformUtils -lLLVMBitWriter -lLLVMAnalysis -lLLVMProfileData -lLLVMSymbolize -lLLVMDebugInfoPDB -lLLVMDebugInfoMSF -lLLVMDebugInfoDWARF -lLLVMObject -lLLVMTextAPI -lLLVMMCParser -lLLVMMC -lLLVMDebugInfoCodeView -lLLVMBitReader -lLLVMCore -lLLVMRemarks -lLLVMBitstreamReader -lLLVMBinaryFormat -lLLVMSupport -lLLVMDemangle
  5. 第五段: llvm-config --system-libs: -lrt -ldl -lpthread -lm -lz -lzstd -ltinfo -lxml2

1.3 编译总览

整体上看使用了GCC、G++编译文件,最后用G++汇总:
在这里插入图片描述

2 加载llvmjit.so

编译后生成llvmjit.so,在程序启动时不做加载,在运行时按需加载。

加载位置:jit.c提供的provider_init函数中,对llvmjit.so进行动态加载:

provider_init
	load_external_function(path, "_PG_jit_provider_init", true, NULL)
		internal_load_library
			dlopen(file_scanner->filename, RTLD_NOW | RTLD_GLOBAL)
		dlsym(lib_handle, funcname);

2.1 如何使用llvmjit.so的符号表?

G++编译的三个cpp文件会依赖llvm库,llvm是用C++实现的,所以llvmjit.so中会存在大量经过mangling的符号,GCC无法识别。

例如:readelf -Ws llvmjit.so | sort -k2 | more
在这里插入图片描述
de-mangling后:nm -C llvmjit.so | sort -k1 | more
在这里插入图片描述
对应:

  • mangling:_Z10isSoftFP16IN4llvm3MVTEEbT_RKNS0_12X86SubtargetE
    • de-mangling:bool isSoftFP16<llvm::MVT>(llvm::MVT, llvm::X86Subtarget const&)
  • mangling:_ZNK4llvm17X86TargetLowering23getStackProbeSymbolNameERKNS_15MachineFunctionE
    • de-mangling:llvm::X86TargetLowering::getStackProbeSymbolName(llvm::MachineFunction const&) const

所以llvmjit.so被动态加载到PG中时,mangling的符号是无法被调用的。那怎么让进程找到符号呢:

用extern "C"标注那些不要做mangling的符号。

例如:llvmjit.h(需要避免mangling的在.h中声明即可)

#ifdef __cplusplus
extern "C"
{
#endif

...

// llvmjit.c
extern LLVMJitContext *llvm_create_context(int jitFlags);
extern char *llvm_expand_funcname(LLVMJitContext *context, const char *basename);
extern void *llvm_get_function(LLVMJitContext *context, const char *funcname);

// llvmjit_error.cpp
extern void llvm_enter_fatal_on_oom(void);


// llvmjit_inline.cpp
extern void llvm_inline_reset_caches(void);
extern void llvm_inline(LLVMModuleRef mod);

...

#ifdef __cplusplus
} /* extern "C" */
#endif

在符号表中可以看到这几个符号:

在这里插入图片描述

这类符号就可以被GCC编译的程序正常调用,例如:llvm_enter_fatal_on_oom函数在llvmjit_error.cpp中定义,在llvmjit_expr.c中使用。

2.2 load llvmjit.so

调用堆栈

#0  jit_compile_expr (state=0x1deae18) at jit.c:180
#1  0x000000000071fa6b in ExecReadyExpr (state=0x1deae18) at execExpr.c:874
#2  0x000000000071e60b in ExecInitExpr (node=0x1dfabb8, parent=0x0) at execExpr.c:152
#3  0x00000000008b3395 in evaluate_expr (expr=0x1dfabb8, result_type=23, result_typmod=-1, result_collation=0) at clauses.c:4892
#4  0x00000000008b26f8 in evaluate_function (funcid=1397, result_type=23, result_typmod=-1, result_collid=0, input_collid=0, args=0x1dfab68, funcvariadic=false, func_tuple=0x7fd9588871a8, context=0x7ffdd8867f20) at clauses.c:4409

...

  • 加载llvmjit.so。
  • 可以由GUC参数制定文件名jit_provider。
jit_compile_expr
	provider_init
		snprintf(path, MAXPGPATH, "%s/%s%s", pkglib_path, jit_provider, DLSUFFIX);
		init = (JitProviderInit)
		load_external_function(path, "_PG_jit_provider_init", true, NULL);

3 llvm_inline流程分析

《Postgresql源码(128)深入分析JIT中的函数内联llvm_inline》

3.1 例子

drop table t1;
create table t1(i int primary key, j int, k int);
insert into t1 select i, i % 10, i % 100 from generate_series(1,100000000) t(i);


set jit_optimize_above_cost to -1;
set jit_above_cost to 0;
set jit_inline_above_cost to 0;
set jit_tuple_deforming to off;
-- jit_inline_above_cost depend on jit_expressions
set jit_expressions to on;

postgres=# explain select abs(k),abs(k),abs(k),abs(k),abs(k),exp(k),exp(k),exp(k),exp(k),exp(k) from t1;
                                   QUERY PLAN
---------------------------------------------------------------------------------
 Seq Scan on t1  (cost=0.00..529063.31 rows=10000175 width=60)
 JIT:
   Functions: 1
   Options: Inlining true, Optimization false, Expressions true, Deforming false


关JIT:9.3秒

                                                    QUERY PLAN                                                     
-------------------------------------------------------------------------------------------------------------------
 Seq Scan on t1  (cost=0.00..529063.31 rows=10000175 width=60) (actual time=0.039..8863.584 rows=10000000 loops=1)
 Planning Time: 0.081 ms
 Execution Time: 9343.666 ms

开JIT:5.9秒

                                                     QUERY PLAN                                                     
--------------------------------------------------------------------------------------------------------------------
 Seq Scan on t1  (cost=0.00..529063.31 rows=10000175 width=60) (actual time=12.100..5460.438 rows=10000000 loops=1)
 Planning Time: 0.081 ms
 JIT:
   Functions: 1
   Options: Inlining true, Optimization false, Expressions true, Deforming false
   Timing: Generation 0.389 ms, Inlining 5.567 ms, Optimization 1.188 ms, Emission 5.301 ms, Total 12.445 ms
 Execution Time: 5921.711 ms
(7 rows)

perf stat数据对比

事件no jitjit分析
task-clock3.924.32执行程序的CPU时间
context-switches1313上下文切换次数,相同
cpu-migrations22CPU迁移次数,相同
page-faults282282内存缺页,相同
cycles9,896,28110,763,253CPU花费的周期
instructions8,902,749(0.90 insn per cycle)9,573,998(0.89 insn per cycle这段时间内完成的CPU指令,每个指令都包含多个步骤,每个步骤由CPU的一个叫做功能单元组件处理,每个步骤至少花费一个cycle周期。insn per cycle表示一个CPU周期内完成几个指令,越高越好,这里没有太大差距。
branches1,719,9401,833,970分支预测的次数
branch-misses51,24754,410分支预测失败的次数
L1-dcache-loads1,976,8353,067,449一级数据缓存读取次数,差距非常大
L1-dcache-load-misses75,05569,671一级数据缓存读取失败次数
LLC-loads52,07054,139last level cache读取次数
LLC-load-misses29,13022,209last level cache读取失败次数,最后一级缓存未命中
总执行时间46.68692507829.740983598

3.2 Inlining与Expressions两种JIT优化分析

上面给的用例函数的编译执行是在投影列中的(无JIT投影列执行可以参考这篇《Postgresql源码(127)投影ExecProject的表达式执行分析》)。

LLVM的执行计划,投影列的计算会进入LLVM堆栈:
在这里插入图片描述

static Datum
ExecRunCompiledExpr(ExprState *state, ExprContext *econtext, bool *isNull)
{
	CompiledExprState *cstate = state->evalfunc_private;
	ExprStateEvalFunc func;

	CheckExprStillValid(state, econtext);

	llvm_enter_fatal_on_oom();
	func = (ExprStateEvalFunc) llvm_get_function(cstate->context,
												 cstate->funcname);
	llvm_leave_fatal_on_oom();
	Assert(func);

	/* remove indirection via this function for future calls */
	state->evalfunc = func;

	return func(state, econtext, isNull);
}

llvm的总入口是llvm_get_function,llvm_get_function→llvm_compile_module→llvm_inline。

下面重点分析inline的过程:

static void
llvm_compile_module(LLVMJitContext *context)
{
	...
	if (context->base.flags & PGJIT_INLINE)
	{
		...
		llvm_inline(context->module);
		...
	}
	...

3.2.1 llvm_inline:入参context->module

module的含义:《LLVM(5)ORC实例分析》
在这里插入图片描述
一个更好理解的例子:《LLVM(1)Fibonacci实例》

3.2.2 module在哪里构造?

llvm_mutable_module创建一个名称为pg的空module:
在这里插入图片描述

LLVMModuleRef
llvm_mutable_module(LLVMJitContext *context)
{
	llvm_assert_in_fatal_section();

	/*
	 * If there's no in-progress module, create a new one.
	 */
	if (!context->module)
	{
		context->compiled = false;
		context->module_generation = llvm_generation++;
		context->module = LLVMModuleCreateWithNameInContext("pg", llvm_context);
		LLVMSetTarget(context->module, llvm_triple);
		LLVMSetDataLayout(context->module, llvm_layout);
	}

	return context->module;
}

然后再llvm_compile_expr中构造表达式计算函数,加到module里面:
在这里插入图片描述

注意这里通过LLVMAddFunction会把表达式计算用到的函数都加进去(只加用到的)。

本例中:

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

llvm_compile_expr函数中LLVMAddFunctionLLVMAddFunction位置
evalexpr_0_0llvm_compile_expr
slot_getsomeattrs_intllvm_compile_expr case EEOP_SCAN_FETCHSOME
int4absllvm_compile_expr case EEOP_FUNCEXPR_STRICT
llvm.lifetime.end.p0i8llvm_compile_expr case EEOP_FUNCEXPR_STRICT
i4todllvm_compile_expr case EEOP_FUNCEXPR_STRICT
dexpllvm_compile_expr case EEOP_FUNCEXPR_STRICT

只有上面5个函数被加到module了,所以后面inline只需要考虑这后四个个函数就好了,第一个是表达式计算入口。

llvm_compile_expr是怎么构造llvm函数的?

《Postgresql源码(113)表达式JIT计算简单分析》

llvm_compile_expr函数是严格对应ExecInterpExpr表达式计算函数,例如计算函数表达式的结果EEOP_FUNCEXPR_STRICT分支:

  • 右侧是PG的表达式计算逻辑。
  • 左侧是对应的JIT build逻辑。
  • 注意,右侧会真正执行,左侧只是build不执行。两边所有分支是一一对应的。
    在这里插入图片描述

3.2.3 llvm_inline函数

void
llvm_inline(LLVMModuleRef M)
{
	LLVMContextRef lc = LLVMGetModuleContext(M);
	// ? 下面有解释
	llvm::Module *mod = llvm::unwrap(M);
  • llvm::unwrap 是一个辅助函数,用于将 C 语言风格的接口转换为 C++ 风格的接口。M是一个来自 LLVM C API 的类型(LLVMModuleRef),这是一个指向 LLVM 模块的指针,但它被封装为一个不透明的指针类型以便在 C 环境中使用。llvm::unwrap 函数将这个不透明的指针转换为一个指向 llvm::Module 的指针,这样就可以在 C++ 代码中使用 LLVM 的 C++ API 来操作这个模块了。
  • (llvm::module 是 LLVM 中的一个类,它代表了一个完整的 LLVM IR模块,这个模块可以包含函数、全局变量、符号等。在 LLVM 的 C++ API 中可以直接使用 llvm::Module 类型的对象)
	
	std::unique_ptr<ImportMapTy> globalsToInline = llvm_build_inline_plan(lc, mod);
	if (!globalsToInline)
		return;
	llvm_execute_inline_plan(mod, globalsToInline.get());
}

3.2.4 准备inline需要执行的部分:lvm_inline→llvm_build_inline_plan

新问题:

  1. 前面 llvm_compile_expr是怎么构造llvm函数的 这部分内容解释了Expressions表达式jit优化的方法。那么下一步就是要执行构造好的module了。
  2. 这里有几个新的问题
    • 问题一:表达式计算已经被剪枝,只保留了需要计算的几个分支,并构造func已经保存module中。但留下的分支中还是需要调用其他PG的C函数才能完成计算,例如abs的计算函数int8abs(),这些函数是C语言编译的,而jit如果需要inline这个int8abs函数的话,是无法直接引用C语言函数的,这里必须把int8abs函数编译成.bc也就是IR CODE才可以整合到llvm的编译中,inline才有可能发生。表达式计算可能会用到很多函数,所以全部的.c文件除了正常cc编译之外,还需要clang编译为.bc文件,保存IR CORE提供给JIT使用。
    • 问题二:拿到函数IR后,是直接无脑inline还是做一些判断再inline?如果目前函数逻辑复杂,调用层次较深,inline的代价是不是比直接执行更大?(决定是否inline,是需要单独计算的)
    • 问题三:inline是PG来做,还是交给LLVM编译器去做?如果交给LLVM编译器做,怎么告知编译器某个函数需要inline?(LLVM编译器做,在IR中配置available_externally标记)

llvm_build_inline_plan 执行流程

static std::unique_ptr<ImportMapTy>
llvm_build_inline_plan(LLVMContextRef lc, llvm::Module *mod)
{
	std::unique_ptr<ImportMapTy> globalsToInline(new ImportMapTy());
	FunctionInlineStates functionStates;
	InlineWorkList worklist;

	InlineSearchPath defaultSearchPath;
  • 回答问题一:注意这里的searchpath是什么,怎么来的请看这篇: 《LLVM的ThinLTO编译优化技术在Postgresql中的应用》
    在这里插入图片描述
	add_module_to_inline_search_path(defaultSearchPath, "$libdir/postgres");
	...
	for (const llvm::Function &funcDecl : mod->functions())
	{
		InlineWorkListItem item = {};
		FunctionInlineState inlineState = {};

  • 只ADD了还没BUILD所以有的函数只有定义,例如evalexpr_0_0llvm.lifetime.end.p0i8
  • 其他函数会正常加入worklist,例如int4absdexp
		if (!funcDecl.isDeclaration())
			continue;
		...

		item.symbolName = funcDecl.getName();
		item.searchpath = defaultSearchPath;
		worklist.push_back(item);
		inlineState.costLimit = inline_initial_cost;
		inlineState.processed = false;
		inlineState.inlined = false;
		inlineState.allowReconsidering = false;
		functionStates[funcDecl.getName()] = inlineState;
	}
  • 本案例中会有四个函数加入worklist:slot_getsomeattrs_int、int4abs、i4tod、dexp
	while (!worklist.empty())
	{
		InlineWorkListItem item = worklist.pop_back_val();
		llvm::StringRef symbolName = item.symbolName;
		char *cmodname;
		char *cfuncname;
		FunctionInlineState &inlineState = functionStates[symbolName];
		llvm::GlobalValue::GUID funcGUID;

		llvm_split_symbol_name(symbolName.data(), &cmodname, &cfuncname);

		funcGUID = llvm::GlobalValue::getGUID(cfuncname);

		for (const auto &gvs : summaries_for_guid(item.searchpath, funcGUID))
		{
  • 从searchpath也就是postgresql.index.bc里面搜索funcGUID,找到modPath。
  • 例如modPath=postgres/utils/adt/float.bc
			const llvm::FunctionSummary *fs;
			llvm::StringRef modPath = gvs->modulePath();
			llvm::Module *defMod;
			llvm::Function *funcDef;

			fs = llvm::cast<llvm::FunctionSummary>(gvs);

			if ((int) fs->instCount() > inlineState.costLimit)
			{
				ilog(DEBUG1, "ineligibile to import %s due to early threshold: %u vs %u",
					 symbolName.data(), fs->instCount(), inlineState.costLimit);
				inlineState.allowReconsidering = true;
				continue;
			}
  • modPath是函数所在的bc文件路径。
  • 在load_module_cached中调用load_module,调用LLVMCreateMemoryBufferWithContentsOfFile、LLVMGetBitcodeModuleInContext2拿到module。
			defMod = load_module_cached(lc, modPath);
			if (defMod->materializeMetadata())
				elog(FATAL, "failed to materialize metadata");

			funcDef = defMod->getFunction(cfuncname);
  • 到这里,通过thinlto生成的index文件的指引,找到函数所在的bc文件,最后拿到了func的bitcode。
			...

			llvm::StringSet<> importVars;
			llvm::SmallPtrSet<const llvm::Function *, 8> visitedFunctions;
			int running_instcount = 0;

  • 回答问题二:拿到了函数的bitcode后确认是否能inline?
  • 能否inline是一系列规则、成本决定的,具体分析在这篇:《Postgresql中JIT函数能否inline的依据function_inlinable》

在这里插入图片描述

			if (function_inlinable(*funcDef,
								   inlineState.costLimit,
								   functionStates,
								   worklist,
								   item.searchpath,
								   visitedFunctions,
								   running_instcount,
								   importVars))
			{
  • 整体指令数不能超过150个,超过就不在inline了,Inline太多会造成代码膨胀。
				if (running_instcount > inlineState.costLimit)
				{
					ilog(DEBUG1, "skipping inlining of %s due to late threshold %d vs %d",
						 symbolName.data(), running_instcount, inlineState.costLimit);
					inlineState.allowReconsidering = true;
					continue;
				}

				ilog(DEBUG1, "inline top function %s total_instcount: %d, partial: %d",
					 symbolName.data(), running_instcount, fs->instCount());

  • 这里把当前函数,例如dexp放到数组中。
  • function_inlinable中没放吗?因为function_inlinable只把dexp调用到的函数放进去了。
  • 把全部需要inline的函数名、全局变量名记录到modGlobalsToInline中。
				importVars.insert(symbolName);

				{
					llvm::StringSet<> &modGlobalsToInline = (*globalsToInline)[modPath];
					for (auto& importVar : importVars)
						modGlobalsToInline.insert(importVar.first());
				}
  • 标记已经Inline
				inlineState.inlined = true;

				break;
			}
			else
			{
				ilog(DEBUG1, "had to skip inlining %s",
					 symbolName.data());
			}
		}

  • 更新FunctionInlineState。
  • 注意:
    • functionStates数组存放了所有inline的函数的信息。
    • globalsToInline是map结构{modPath, StringSet},根据"postgres/utils/adt/float.bc"找到StringSet,在StringSet中保存了dexp、dexp函数调用到的其他函数、dexp和调用到函数所有使用到的全局变量。
		inlineState.processed = true;
	}

	return globalsToInline;
}

3.2.5 执行inline的部分:lvm_inline→llvm_execute_inline_plan

遍历上述结果集中的函数,如果确定最终需要inline的,配置AvailableExternallyLinkage标记即可。其他留给llvm编译器去做。

static void
llvm_execute_inline_plan(llvm::Module *mod, ImportMapTy *globalsToInline)
{
	...
	for (const auto& toInline : *globalsToInline)
	{
		const llvm::StringRef& modPath = toInline.first();
		const llvm::StringSet<>& modGlobalsToInline = toInline.second;

		std::unique_ptr<llvm::Module> importMod(std::move((*module_cache)[modPath]));
		module_cache->erase(modPath);

		for (auto &glob: modGlobalsToInline)
		{
			llvm::StringRef SymbolName = glob.first();

			llvm::GlobalValue *valueToImport = importMod->getNamedValue(funcname);

			if (llvm::isa<llvm::Function>(valueToImport))
			{
				llvm::Function *F = llvm::dyn_cast<llvm::Function>(valueToImport);
				typedef llvm::GlobalValue::LinkageTypes LinkageTypes;

				...

				if (valueToImport->hasExternalLinkage())
				{
					valueToImport->setLinkage(LinkageTypes::AvailableExternallyLinkage);
				}
			}
		}
	}
}

https://llvm.org/docs/LangRef.html#linkage-types

在这里插入图片描述
在这里插入图片描述

  • 默认link方式 ExternalLinkage: 符号可能在其他模块中被引用,LLVM 在决定是否内联会更加谨慎。可能不会内联这些符号,因为它需要确保符号在模块外部仍然可用。
  • 主动修改为 AvailableExternallyLinkage: 从链接器的角度来看,available_externally 全局变量相当于外部声明。存在的目的是进行内联和其他优化。

GlobalValue.h

class GlobalValue : public Constant {
public:
  /// An enumeration for the kinds of linkage for global values.
  enum LinkageTypes {
    ExternalLinkage = 0,///< Externally visible function
    AvailableExternallyLinkage, ///< Available for inspection, not emission.    <<<<<<<<<
    LinkOnceAnyLinkage, ///< Keep one copy of function when linking (inline)
    LinkOnceODRLinkage, ///< Same, but only replaced by something equivalent.
    WeakAnyLinkage,     ///< Keep one copy of named function when linking (weak)
    WeakODRLinkage,     ///< Same, but only replaced by something equivalent.
    AppendingLinkage,   ///< Special purpose, only applies to global arrays
    InternalLinkage,    ///< Rename collisions when linking (static functions).
    PrivateLinkage,     ///< Like Internal, but omit from symbol table.
    ExternalWeakLinkage,///< ExternalWeak linkage description.
    CommonLinkage       ///< Tentative definitions.
  };

当函数被标记为AvailableExternallyLinkage时,LLVM优化器有可能会内联这些函数。但不是一定会发生,内联决策是llvm内联启发式算法做出的,会考虑很多因素:函数的大小、复杂性、调用频率、调用上下文等等。如果llvm决定内联一个函数,它会将函数的代码直接插入到每个调用点。

从module里面读取一下inline后的IR代码,发现函数已经有了available_externally标记。
在这里插入图片描述

3.2.6 内联后的效果

决定不做内联
在这里插入图片描述
优化后,还是正常调用:
在这里插入图片描述

决定内联
在这里插入图片描述

原来调用的位置变成什么了?
在这里插入图片描述

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

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

相关文章

Maven多环境打包方法配置

简单记录一下SpringBoot多环境打包配置方法&#xff0c;分部署环境和是否包含lib依赖包两个维度 目录 一、需求说明二、目录结构三、配置方案四、验证示例 一、需求说明 基于Spring Boot框架的项目分开发&#xff0c;测试&#xff0c;生产等编译部署环境&#xff08;每一个环境…

SDL2基本使用

前言 在这里记录SDL的环境基本搭建和使用&#xff0c;方便回忆。使用该图形库也是为了方便在没有单片机和显示模块的使用&#xff0c;也能对简单验证些关于图形构建或界面管理的猜想和测试&#xff0c;所以下述不会探讨过于深入的东西。当然&#xff0c;也可以通过SDL官网查看介…

【Linux系统编程】—— 从零开始实现一个简单的自定义Shell

文章目录 什么是自主shell命令行解释器&#xff1f;实现shell的基础认识全局变量的配置初始化环境变量实现内置命令&#xff08;如 cd 和 echo&#xff09;cd命令&#xff1a;echo命令&#xff1a; 构建命令行提示符获取并解析用户输入的命令执行内置命令与外部命令Shell的主循…

认识BOM

BOM 弹出层 可视窗口尺寸 屏幕宽高 浏览器内核和其操作系统的版本 剪贴板 是否允许使用cookie 语言 是否在线

[c语言日寄]结构体的使用及其拓展

【作者主页】siy2333 【专栏介绍】⌈c语言日寄⌋&#xff1a;这是一个专注于C语言刷题的专栏&#xff0c;精选题目&#xff0c;搭配详细题解、拓展算法。从基础语法到复杂算法&#xff0c;题目涉及的知识点全面覆盖&#xff0c;助力你系统提升。无论你是初学者&#xff0c;还是…

Linux系统的第一个进程是什么?

Linux进程的生命周期从创建开始&#xff0c;直至终止&#xff0c;贯穿了一个进程的整个存在过程。我们可以通过系统调用fork()或vfork()来创建一个新的子进程&#xff0c;这标志着一个新进程的诞生。 实际上&#xff0c;Linux系统中的所有进程都是由其父进程创建的。 既然所有…

5. 马科维茨资产组合模型+AI金融智能体(qwen-max)识别政策意图方案(理论+Python实战)

目录 0. 承前1. AI金融智能体1.1 What is AI金融智能体1.2 Why is AI金融智能体1.3 How to AI金融智能体 2. 数据要素&计算流程2.1 参数集设置2.2 数据获取&预处理2.3 收益率计算2.4 因子构建与预期收益率计算2.5 协方差矩阵计算2.6 投资组合优化2.7 持仓筛选2.8 AI金融…

PostMan最新版本及离线安装指南

本文还有配套的精品资源&#xff0c;点击获取 简介&#xff1a;PostMan是一款流行的API测试工具&#xff0c;它提供了一个直观的用户界面&#xff0c;方便Web开发者和测试人员进行接口测试。本文将指导你如何安装最新版的PostMan&#xff0c;包括在线安装和离线安装两种方法。…

记录一次k8s起不来的排查过程

我在k8s集群&#xff0c;重启了一个node宿主机&#xff0c;竟然发现kubelet起不来了&#xff01;报错如下 这个报错很模糊&#xff0c;怎么排查呢。这样&#xff0c;开两个界面&#xff0c;一个重启kubelet&#xff0c;一个看系统日志(/var/log/message:centos&#xff0c;/va…

grafana + Prometheus + node_exporter搭建监控大屏

本文介绍生产系统监控大屏的搭建&#xff0c;比较实用也是实际应用比较多的方式&#xff0c;希望能够帮助大家对监控系统有一定的认识。 0、规划 grafana主要是展示和报警&#xff0c;Prometheus用于保存监控数据&#xff0c;node_exporter用于实时采集各个应用服务器的事实状…

2024年博客之星主题创作|从零到一:我的技术成长与创作之路

2024年博客之星主题创作&#xff5c;从零到一&#xff1a;我的技术成长与创作之路 个人简介个人主页个人成就热门专栏 历程回顾初来CSDN&#xff1a;怀揣憧憬&#xff0c;开启创作之旅成长之路&#xff1a;从平凡到榜一的蜕变持续分享&#xff1a;打卡基地与成长复盘四年历程&a…

Golang的网络编程安全

Golang的网络编程安全 一、Golang网络编程的基本概念 作为一种现代化的编程语言&#xff0c;具有优秀的并发特性和网络编程能力。在Golang中&#xff0c;网络编程是非常常见的需求&#xff0c;可以用于开发各种类型的网络应用&#xff0c;比如Web服务、API服务、消息队列等。Go…

【2024年华为OD机试】(C/D卷,200分)- 5G网络建设 (JavaScriptJava PythonC/C++)

一、问题描述 题目描述 现需要在某城市进行5G网络建设&#xff0c;已经选取N个地点设置5G基站&#xff0c;编号固定为1到N。接下来需要各个基站之间使用光纤进行连接以确保基站能互联互通。不同基站之间假设光纤的成本各不相同&#xff0c;且有些节点之间已经存在光纤相连。 …

消息队列篇--原理篇--RabbitMQ和Kafka对比分析

RabbitMQ和Kafka是两种非常流行的消息队列系统&#xff0c;但它们的设计哲学、架构特点和适用场景存在显著差异。对比如下。 1、架构设计 RabbitMQ&#xff1a; 基AMQP协议&#xff1a;RabbitMQ是基于AMQP&#xff08;高级消息队列协议&#xff09;构建的&#xff0c;支持多…

玻璃样式的登录界面

AI越来越火了,我们想要不被淘汰就得主动拥抱。推荐一个人工智能学习网站,通俗易懂,风趣幽默,最重要的屌图甚多,忍不住分享一下给大家。点击跳转到网站 先看样式: 源码: <div class="wrapper">

Python数据可视化(够用版):懂基础 + 专业的图表抛给Tableau等专业绘图工具

我先说说文章标题中的“够用版”啥意思&#xff0c;为什么这么写。 按照我个人观点&#xff0c;在使用Python进行数据分析时&#xff0c;我们有时候肯定要结合到图表去进行分析&#xff0c;去直观展现数据的规律和特定&#xff0c;那么我们肯定要做一些简单的可视化&#xff0…

物联网网关Web服务器--CGI开发实例BMI计算

本例子通一个计算体重指数的程序来演示Web服务器CGI开发。 硬件环境&#xff1a;飞腾派开发板&#xff08;国产E2000处理器&#xff09; 软件环境&#xff1a;飞腾派OS&#xff08;Phytium Pi OS&#xff09; 硬件平台参考另一篇博客&#xff1a;国产化ARM平台-飞腾派开发板…

HTML新春烟花

系列文章 序号目录1HTML满屏跳动的爱心&#xff08;可写字&#xff09;2HTML五彩缤纷的爱心3HTML满屏漂浮爱心4HTML情人节快乐5HTML蓝色爱心射线6HTML跳动的爱心&#xff08;简易版&#xff09;7HTML粒子爱心8HTML蓝色动态爱心9HTML跳动的爱心&#xff08;双心版&#xff09;1…

从结构嵌套的幻梦里:递归与数据构建的精巧和鸣

大家好啊&#xff0c;我是小象٩(๑ω๑)۶ 我的博客&#xff1a;Xiao Xiangζั͡ޓއއ 很高兴见到大家&#xff0c;希望能够和大家一起交流学习&#xff0c;共同进步。 这一节我们来学习递归的相关知识 函数递归 一、什么是递归1.1 递归的思想 二、递归的限制条件三、递归的…

【Linux系统】—— 编译器 gcc/g++ 的使用

【Linux系统】—— 编译器 gcc/g 的使用 1 用 gcc 直接编译2 翻译环境2.1 预处理&#xff08;进行宏替换&#xff09;2.2 编译&#xff08;生成汇编&#xff09;2.3 汇编&#xff08;生成机器可识别代码&#xff09;2.4 链接2.5 记忆小技巧2.6 编译方式2.7 几个问题2.7.1 如何理…