赞
踩
这一章解释什么是即时编译以及如何在PostgreSQL中配置即时编译。
即时(Just-In-Time,JIT)编译是将某种形式的解释程序计算转变成原生程序的过程,并且这一过程是在运行时完成的。例如,与使用能够计算任意SQL表达式的通用代码来计算一个特定的SQL谓词(如WHERE a.col = 3
)不同,可以产生一个专门针对该表达式的函数并且可以由CPU原生执行,从而得到加速。
当使用--with-llvm编译PostgreSQL后,PostgreSQL内建支持用LLVM执行JIT编译。
进一步的细节请参考src/backend/jit/README
。
当前,PostgreSQL的JIT实现支持对表达式计算以及元组拆解的加速。未来可能有更多其他操作采用这种技术加速。
表达式计算被用来计算WHERE
子句、目标列表、聚集以及投影。通过为每一种情况生成专门的代码来实现加速。
元组拆解是把一个磁盘上的元组转换成其在内存中表示的过程。通过创建一个专门针对该表布局和要被抽取的列数的函数来实现加速。
PostgreSQL有很好的扩展性并且允许定义新的数据类型、函数、操作符以及其他数据库对象。实际上,内建对象都使用近乎完全相同的机制来实现。这种可扩展性隐含了一些开销,例如函数调用带来的开销。为了降低这类开销,JIT编译可以把小函数的函数体内联到使用它们的表达式中。这种方式可以优化掉可观的开销。
LLVM支持对生成的代码进行优化。一些优化代价很低,以至于可以在每次使用JIT时都执行,而另一些优化则只有在运行时间较长的查询中才能获益。更多有关优化的细节请参考LLVM’s Analysis and Transform Passes — LLVM 19.0.0git documentation。
JIT编译主要可以让长时间运行的CPU密集型的查询受益。对于短查询,执行JIT编译增加的开销常常比它节省的时间还要多。
为了判断是否应该使用JIT编译,会用到一个查询的总的估计代价。查询的估计代价将与jit_above_cost的设置进行比较。如果代价更高,JIT编译将被执行。然后需要两个进一步的决定。首先,如果估计代价超过jit_inline_above_cost的设置,该查询中使用的短函数和操作符都将被内联。其次,如果估计代价超过jit_optimize_above_cost的设置,会应用昂贵的优化来改进产生的代码。这些选项中的每一种都会增加JIT编译的开销,但是可以可观地降低查询执行时间。
这些基于代价的决定将在规划时做出,而不是在执行时做出。这意味着,在使用预备语句并且使用了一个一般性的计划时(见PREPARE),配置参数的值实际上是在预备时控制这些决定,而不是由执行时的设置来决定。
注意:
如果jit被设置为
off
或者没有JIT实现可用(例如因为服务器没有用--with-llvm
编译),即便基于上述原则能带来很大的好处,JIT也不会被执行。把jit设置成off
对规划时和执行时都有影响。
EXPAIN可以被用来看看是否使用了JIT。例如,这是一个没有使用JIT的查询:
- =# EXPLAIN ANALYZE SELECT SUM(relpages) FROM pg_class;
- QUERY PLAN
- -------------------------------------------------------------------------------------------------------------
- Aggregate (cost=16.27..16.29 rows=1 width=8) (actual time=0.303..0.303 rows=1 loops=1)
- -> Seq Scan on pg_class (cost=0.00..15.42 rows=342 width=4) (actual time=0.017..0.111 rows=356 loops=1)
- Planning Time: 0.116 ms
- Execution Time: 0.365 ms
- (4 rows)
看看给出的计划代价,不使用JIT是非常合理的,JIT的代价会比可能得到的节省更高。调整代价限制会导致用到JIT:
- =# SET jit_above_cost = 10;
- SET
- =# EXPLAIN ANALYZE SELECT SUM(relpages) FROM pg_class;
- QUERY PLAN
- -------------------------------------------------------------------------------------------------------------
- Aggregate (cost=16.27..16.29 rows=1 width=8) (actual time=6.049..6.049 rows=1 loops=1)
- -> Seq Scan on pg_class (cost=0.00..15.42 rows=342 width=4) (actual time=0.019..0.052 rows=356 loops=1)
- Planning Time: 0.133 ms
- JIT:
- Functions: 3
- Options: Inlining false, Optimization false, Expressions true, Deforming true
- Timing: Generation 1.259 ms, Inlining 0.000 ms, Optimization 0.797 ms, Emission 5.048 ms, Total 7.104 ms
- Execution Time: 7.416 ms
如这里所看到的,JIT被用到了,但是内联和昂贵的优化没有被用到。如果jit_inline_above_cost或者jit_optimize_above_cost也被降低,这种情况会被改变。
配置变量jit决定启用或者禁用JIT编译。如果它被启用,配置变量jit_above_cost、jit_inline_above_cost以及jit_optimize_above_cost判断是否要为一个查询执行JIT编译以及在执行中花费多大的努力。
jit_provider决定使用哪一种JIT实现。很少需要改变这一设置。
对于开发和调试目的,还有一些额外的配置参数存在。
PostgreSQL的JIT实现可以内联C
以及internal
类型的函数体,还有基于这类函数的操作符。为了能对扩展中的函数这样做,需要让那些函数的定义可用。在使用PGSX对一个已经编译有LLVM JIT支持的服务器构建一个扩展时,相关的文件将被自动构建并且安装。
相关的文件必须被安装在$pkglibdir/bitcode/$extension/
中并且对它们的一份概要必须被安装在$pkglibdir/bitcode/$extension.index.bc
中,其中$pkglibdir
是pg_config --pkglibdir
返回的目录里,而$extension
是扩展的共享库的基础名称。
注意:
对于编译在PostgreSQL本身中的函数,其bitcode被安装在
$pkglibdir/bitcode/postgres
。
PostgreSQL提供一种基于LLVM的JIT实现。JIT提供者的接口是可插拔的,可以无需重编译就能改变提供者(尽管当前构建过程仅提供了对LLVM的内联支持数据)。活跃的提供者通过设置jit_provider来选择。
JIT提供者需要通过动态装载其共享库来载入。正常的搜索路径被用来定位该库。为了提供所要求的JIT提供者回调并且表示该库实际上是一个JIT提供者,它需要提供一个名为_PG_jit_provider_init
的C函数。会有一个结构被传入这个函数,在函数中应该用回调函数指针来填充该结构:
- struct JitProviderCallbacks
- {
- JitProviderResetAfterErrorCB reset_after_error;
- JitProviderReleaseContextCB release_context;
- JitProviderCompileExprCB compile_expr;
- };
-
- extern void _PG_jit_provider_init(JitProviderCallbacks *cb);
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。