歴史的に、PostgreSQLはPL / pgSQL関数の事前コンパイルの形でコンパイル機能を提供し、バージョン10では式のコンパイルが導入されました。ただし、これらはいずれもマシンコードを生成しません。
JIT for SQLは何年も前に議論されましたが、PostgreSQLの場合、この機能は大幅なコード変更の結果です。
PostgreSQLバイナリがLLVMサポートを使用して構築されているかどうかを確認するには、pg_configureコマンドを使用してコンパイルフラグを表示し、出力で–with-llvmを探します。 PGDG RPMディストリビューションの例:
omiday ~ $ /usr/pgsql-11/bin/pg_config --configure
'--enable-rpath' '--prefix=/usr/pgsql-11' '--includedir=/usr/pgsql-11/include' '--mandir=/usr/pgsql-11/share/man' '--datadir=/usr/pgsql-11/share' '--enable-tap-tests' '--with-icu' '--with-llvm' '--with-perl' '--with-python' '--with-tcl' '--with-tclconfig=/usr/lib64' '--with-openssl' '--with-pam' '--with-gssapi' '--with-includes=/usr/include' '--with-libraries=/usr/lib64' '--enable-nls' '--enable-dtrace' '--with-uuid=e2fs' '--with-libxml' '--with-libxslt' '--with-ldap' '--with-selinux' '--with-systemd' '--with-system-tzdata=/usr/share/zoneinfo' '--sysconfdir=/etc/sysconfig/pgsql' '--docdir=/usr/pgsql-11/doc' '--htmldir=/usr/pgsql-11/doc/html' 'CFLAGS=-O2 -g -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector-strong -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection' 'PKG_CONFIG_PATH=:/usr/lib64/pkgconfig:/usr/share/pkgconfig'
なぜLLVMJITなのか?
Adres Freundの投稿で説明されているように、式の評価とタプルの変形が大規模なクエリを高速化する際の障害であることが判明したとき、すべては約2年前に始まりました。 JIT実装を追加した後、Andresの言葉で「式の評価自体は以前より10倍以上高速になりました」。さらに、彼の投稿を終了するQ&Aセクションでは、他の実装よりもLLVMを選択する方法について説明しています。
LLVMが選択されたプロバイダーでしたが、GUCパラメーターjit_providerを使用して別のJITプロバイダーを指すことができます。ただし、インライン化のサポートは、ビルドプロセスの動作方法により、LLVMプロバイダーを使用している場合にのみ利用可能であることに注意してください。
いつJITするのですか?
ドキュメントは明確です。CPUにバインドされた長時間実行クエリは、JITコンパイルの恩恵を受けます。さらに、このブログ全体で参照されているメーリングリストのディスカッションでは、JITは、一度だけ実行されるクエリには高すぎると指摘されています。
プログラミング言語と比較して、PostgreSQLには、クエリプランナーに依存することにより、いつJITを実行するかを「知る」という利点があります。そのために、いくつかのGUCパラメーターが導入されました。 JITを有効にする際のネガティブな驚きからユーザーを保護するために、コスト関連のパラメーターは意図的に適度に高い値に設定されています。 JITコストパラメータを「0」に設定すると、すべてのクエリが強制的にJITコンパイルされ、その結果、すべてのクエリの速度が低下することに注意してください。
JITは一般的に有益ですが、commit b9f2d4d3で説明されているように、JITを有効にすると有害になる場合があります。
JITの方法は?
上で触れたように、RPMバイナリパッケージはLLVM対応です。ただし、JITコンパイルを機能させるには、いくつかの追加手順が必要です。
ウィットに:
[email protected][local]:54311 test# show server_version;
server_version
----------------
11.1
(1 row)
[email protected][local]:54311 test# show port;
port
-------
54311
(1 row)
[email protected][local]:54311 test# create table t1 (id serial);
CREATE TABLE
[email protected][local]:54311 test# insert INTO t1 (id) select * from generate_series(1, 10000000);
INSERT 0 10000000
[email protected][local]:54311 test# set jit = 'on';
SET
[email protected][local]:54311 test# set jit_above_cost = 10; set jit_inline_above_cost = 10; set jit_optimize_above_cost = 10;
SET
SET
SET
[email protected][local]:54311 test# explain analyze select count(*) from t1;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------
Finalize Aggregate (cost=97331.43..97331.44 rows=1 width=8) (actual time=647.585..647.585 rows=1 loops=1)
-> Gather (cost=97331.21..97331.42 rows=2 width=8) (actual time=647.484..649.059 rows=3 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial Aggregate (cost=96331.21..96331.22 rows=1 width=8) (actual time=640.995..640.995 rows=1 loops=3)
-> Parallel Seq Scan on t1 (cost=0.00..85914.57 rows=4166657 width=0) (actual time=0.060..397.121 rows=3333333 loops=3)
Planning Time: 0.182 ms
Execution Time: 649.170 ms
(8 rows)
JITを有効にしたことに注意してください(コミットb9f2d4d3で参照されているpgsql-hackersの説明に従ってデフォルトで無効になっています)。ドキュメントで提案されているように、JITパラメータのコストも調整しました。
最初のヒントは、JITドキュメントで参照されているsrc / backend / jit/READMEファイルにあります。
Which shared library is loaded is determined by the jit_provider GUC, defaulting to "llvmjit".
RPMパッケージはJIT依存関係を自動的に取得しないため、徹底的な議論の結果(スレッド全体を参照)に決定されたため、手動でインストールする必要があります。
[[email protected] ~]# dnf install postgresql11-llvmjit
インストールが完了したら、すぐにテストできます:
[email protected][local]:54311 test# explain analyze select count(*) from t1;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------
Finalize Aggregate (cost=97331.43..97331.44 rows=1 width=8) (actual time=794.998..794.998 rows=1 loops=1)
-> Gather (cost=97331.21..97331.42 rows=2 width=8) (actual time=794.870..803.680 rows=3 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial Aggregate (cost=96331.21..96331.22 rows=1 width=8) (actual time=689.124..689.125 rows=1 loops=3)
-> Parallel Seq Scan on t1 (cost=0.00..85914.57 rows=4166657 width=0) (actual time=0.062..385.278 rows=3333333 loops=3)
Planning Time: 0.150 ms
JIT:
Functions: 4
Options: Inlining true, Optimization true, Expressions true, Deforming true
Timing: Generation 2.146 ms, Inlining 117.725 ms, Optimization 47.928 ms, Emission 69.454 ms, Total 237.252 ms
Execution Time: 803.789 ms
(12 rows)
ワーカーごとのJITの詳細を表示することもできます:
[email protected][local]:54311 test# explain (analyze, verbose, buffers) select count(*) from t1;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------
Finalize Aggregate (cost=97331.43..97331.44 rows=1 width=8) (actual time=974.352..974.352 rows=1 loops=1)
Output: count(*)
Buffers: shared hit=2592 read=41656
-> Gather (cost=97331.21..97331.42 rows=2 width=8) (actual time=974.166..980.942 rows=3 loops=1)
Output: (PARTIAL count(*))
Workers Planned: 2
Workers Launched: 2
JIT for worker 0:
Functions: 2
Options: Inlining true, Optimization true, Expressions true, Deforming true
Timing: Generation 0.378 ms, Inlining 74.033 ms, Optimization 11.979 ms, Emission 9.470 ms, Total 95.861 ms
JIT for worker 1:
Functions: 2
Options: Inlining true, Optimization true, Expressions true, Deforming true
Timing: Generation 0.319 ms, Inlining 68.198 ms, Optimization 8.827 ms, Emission 9.580 ms, Total 86.924 ms
Buffers: shared hit=2592 read=41656
-> Partial Aggregate (cost=96331.21..96331.22 rows=1 width=8) (actual time=924.936..924.936 rows=1 loops=3)
Output: PARTIAL count(*)
Buffers: shared hit=2592 read=41656
Worker 0: actual time=900.612..900.613 rows=1 loops=1
Buffers: shared hit=668 read=11419
Worker 1: actual time=900.763..900.763 rows=1 loops=1
Buffers: shared hit=679 read=11608
-> Parallel Seq Scan on public.t1 (cost=0.00..85914.57 rows=4166657 width=0) (actual time=0.311..558.192 rows=3333333 loops=3)
Output: id
Buffers: shared hit=2592 read=41656
Worker 0: actual time=0.389..539.796 rows=2731662 loops=1
Buffers: shared hit=668 read=11419
Worker 1: actual time=0.082..548.518 rows=2776862 loops=1
Buffers: shared hit=679 read=11608
Planning Time: 0.207 ms
JIT:
Functions: 9
Options: Inlining true, Optimization true, Expressions true, Deforming true
Timing: Generation 8.818 ms, Inlining 153.087 ms, Optimization 77.999 ms, Emission 64.884 ms, Total 304.787 ms
Execution Time: 989.360 ms
(36 rows)
JIT実装は、並列クエリ実行機能を利用することもできます。例として、最初に並列化を無効にしましょう:
[email protected][local]:54311 test# set max_parallel_workers_per_gather = 0;
SET
[email protected][local]:54311 test# explain analyze select count(*) from t1;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------
Aggregate (cost=169247.71..169247.72 rows=1 width=8) (actual time=1447.315..1447.315 rows=1 loops=1)
-> Seq Scan on t1 (cost=0.00..144247.77 rows=9999977 width=0) (actual time=0.064..957.563 rows=10000000 loops=1)
Planning Time: 0.053 ms
JIT:
Functions: 2
Options: Inlining true, Optimization true, Expressions true, Deforming true
Timing: Generation 0.388 ms, Inlining 1.359 ms, Optimization 7.626 ms, Emission 7.963 ms, Total 17.335 ms
Execution Time: 1447.783 ms
(8 rows)
並列クエリを有効にした同じコマンドは、半分の時間で完了します。
[email protected][local]:54311 test# reset max_parallel_workers_per_gather ;
RESET
[email protected][local]:54311 test# explain analyze select count(*) from t1;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------
Finalize Aggregate (cost=97331.43..97331.44 rows=1 width=8) (actual time=707.126..707.126 rows=1 loops=1)
-> Gather (cost=97331.21..97331.42 rows=2 width=8) (actual time=706.971..712.199 rows=3 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial Aggregate (cost=96331.21..96331.22 rows=1 width=8) (actual time=656.102..656.103 rows=1 loops=3)
-> Parallel Seq Scan on t1 (cost=0.00..85914.57 rows=4166657 width=0) (actual time=0.067..384.207 rows=3333333 loops=3)
Planning Time: 0.158 ms
JIT:
Functions: 9
Options: Inlining true, Optimization true, Expressions true, Deforming true
Timing: Generation 3.709 ms, Inlining 142.150 ms, Optimization 50.983 ms, Emission 33.792 ms, Total 230.634 ms
Execution Time: 715.226 ms
(12 rows)
JIT実装の初期段階と最終バージョンで、この投稿で説明したテストの結果を比較するのは興味深いと思いました。まず、元のテストの条件が満たされていることを確認します。つまり、データベースがメモリに収まっている必要があります。
[email protected][local]:54311 test# \l+
postgres | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | | 8027 kB | pg_default | default administrative connection database
template0 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +| 7889 kB | pg_default | unmodifiable empty database
| | | | | postgres=CTc/postgres | | |
template1 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +| 7889 kB | pg_default | default template for new databases
| | | | | postgres=CTc/postgres | | |
test | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | | 2763 MB | pg_default |
[email protected][local]:54311 test# show shared_buffers ;
3GB
Time: 0.485 ms
今日のホワイトペーパーをダウンロードするClusterControlを使用したPostgreSQLの管理と自動化PostgreSQLの導入、監視、管理、スケーリングを行うために知っておくべきことについて学ぶホワイトペーパーをダウンロードする JITを無効にしてテストを実行します:
[email protected][local]:54311 test# set jit = off;
SET
Time: 0.483 ms
[email protected][local]:54311 test# select sum(c8) from t1;
0
Time: 1036.231 ms (00:01.036)
[email protected][local]:54311 test# select sum(c2), sum(c3), sum(c4), sum(c5),
sum(c6), sum(c7), sum(c8) from t1;
0 | 0 | 0 | 0 | 0 | 0 | 0
Time: 1793.502 ms (00:01.794)
次に、JITを有効にしてテストを実行します:
[email protected][local]:54311 test# set jit = on; set jit_above_cost = 10; set
jit_inline_above_cost = 10; set jit_optimize_above_cost = 10;
SET
Time: 0.473 ms
SET
Time: 0.267 ms
SET
Time: 0.204 ms
SET
Time: 0.162 ms
[email protected][local]:54311 test# select sum(c8) from t1;
0
Time: 795.746 ms
[email protected][local]:54311 test# select sum(c2), sum(c3), sum(c4), sum(c5),
sum(c6), sum(c7), sum(c8) from t1;
0 | 0 | 0 | 0 | 0 | 0 | 0
Time: 1080.446 ms (00:01.080)
これは、最初のテストケースで約25%、2番目のテストケースで40%のスピードアップです!
最後に、プリペアドステートメントの場合、関数が最初に実行されるときにJITコンパイルが実行されることを覚えておくことが重要です。
結論
デフォルトでは、JITコンパイルは無効になっており、RPMベースのシステムの場合、インストーラーは、デフォルトプロバイダーLLVMのビットコードを提供するJITパッケージをインストールする必要があることを示唆しません。
ソースからビルドする場合、たとえばLLVMアサーションが有効になっている場合など、パフォーマンスの問題を回避するためにコンパイルフラグに注意してください。
pgsql-hackersリストで説明されているように、コストに対するJITの影響はまだ完全には理解されていないため、コンパイルの恩恵を受ける可能性のあるクエリは実際には遅くなる可能性があるため、機能クラスター全体を有効にする前に慎重な計画が必要です。ただし、JITはクエリごとに有効にすることができます。
JITコンパイルの実装に関する詳細については、プロジェクトのGitログ、Commitfests、およびpgsql-hackersメールスレッドを確認してください。
Happy JITing!