Historiquement, PostgreSQL a fourni des fonctionnalités de compilation sous la forme d'une compilation anticipée pour les fonctions PL/pgSQL et la version 10 a introduit la compilation d'expressions. Cependant, aucun de ceux-ci ne génère de code machine.
JIT pour SQL a été discuté il y a de nombreuses années, et pour PostgreSQL, la fonctionnalité est le résultat d'un changement de code substantiel.
Pour vérifier si le binaire PostgreSQL a été construit avec le support LLVM, utilisez la commande pg_configure pour afficher les drapeaux de compilation et recherchez –with-llvm dans la sortie. Exemple pour la distribution RPM PGDG :
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'
Pourquoi LLVM JIT ?
Tout a commencé il y a environ deux ans, comme expliqué dans l'article d'Adres Freund, lorsque l'évaluation de l'expression et la déformation des tuples se sont avérées être les obstacles à l'accélération des requêtes volumineuses. Après avoir ajouté l'implémentation JIT, "l'évaluation de l'expression elle-même est plus de dix fois plus rapide qu'avant" selon les mots d'Andres. De plus, la section Q&A qui termine son article explique le choix de LLVM par rapport à d'autres implémentations.
Alors que LLVM était le fournisseur choisi, le paramètre GUC jit_provider peut être utilisé pour pointer vers un autre fournisseur JIT. Notez cependant que la prise en charge de l'intégration n'est disponible que lors de l'utilisation du fournisseur LLVM, en raison du fonctionnement du processus de construction.
Quand JAT ?
La documentation est claire :les requêtes longues qui sont liées au processeur bénéficieront de la compilation JIT. De plus, les discussions sur les listes de diffusion référencées tout au long de ce blog soulignent que le JIT est trop coûteux pour les requêtes qui ne sont exécutées qu'une seule fois.
Par rapport aux langages de programmation, PostgreSQL a l'avantage de "savoir" quand JIT, en s'appuyant sur le planificateur de requêtes. À cet effet, un certain nombre de paramètres GUC ont été introduits. Pour protéger les utilisateurs des mauvaises surprises lors de l'activation du JIT, les paramètres liés au coût sont intentionnellement définis sur des valeurs raisonnablement élevées. Notez que définir les paramètres de coût JIT sur "0" forcera toutes les requêtes à être compilées JIT et, par conséquent, ralentira toutes vos requêtes.
Bien que JIT puisse être généralement bénéfique, il existe des cas où son activation peut être préjudiciable, comme indiqué dans le commit b9f2d4d3.
Comment JAT ?
Comme mentionné ci-dessus, les packages binaires RPM sont compatibles LLVM. Cependant, pour que la compilation JIT fonctionne, quelques étapes supplémentaires sont nécessaires :
A savoir :
[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)
Notez que j'ai activé JIT (qui est désactivé par défaut suite à la discussion pgsql-hackers référencée dans le commit b9f2d4d3). J'ai également ajusté le coût des paramètres JIT comme suggéré dans la documentation.
Le premier indice se trouve dans le fichier src/backend/jit/README référencé dans la documentation JIT :
Which shared library is loaded is determined by the jit_provider GUC, defaulting to "llvmjit".
Étant donné que le package RPM n'intègre pas automatiquement la dépendance JIT - comme cela a été décidé après des discussions approfondies (voir le fil de discussion complet) - nous devons l'installer manuellement :
[[email protected] ~]# dnf install postgresql11-llvmjit
Une fois l'installation terminée, nous pouvons tester immédiatement :
[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)
Nous pouvons également afficher les détails JIT par travailleur :
[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)
L'implémentation JIT peut également tirer parti de la fonctionnalité d'exécution de requêtes parallèles. Pour illustrer, commençons par désactiver la parallélisation :
[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)
La même commande avec les requêtes parallèles activées se termine en deux fois moins de temps :
[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)
J'ai trouvé intéressant de comparer les résultats des tests discutés dans cet article, au cours des premières étapes de la mise en œuvre du JIT par rapport à la version finale. Assurez-vous d'abord que les conditions du test d'origine sont remplies, c'est-à-dire que la base de données doit tenir en mémoire :
[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
Téléchargez le livre blanc aujourd'hui PostgreSQL Management &Automation with ClusterControlDécouvrez ce que vous devez savoir pour déployer, surveiller, gérer et faire évoluer PostgreSQLTélécharger le livre blanc Exécutez les tests avec JIT désactivé :
[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)
Exécutez ensuite les tests avec JIT activé :
[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)
Cela représente une accélération d'environ 25 % pour le premier cas de test et de 40 % pour le second !
Enfin, il est important de se rappeler que pour les instructions préparées, la compilation JIT est effectuée lors de la première exécution de la fonction.
Conclusion
Par défaut, la compilation JIT est désactivée et, pour les systèmes basés sur RPM, le programme d'installation n'indiquera pas la nécessité d'installer le package JIT fournissant le code binaire pour le fournisseur par défaut LLVM.
Lors de la construction à partir de sources, faites attention aux indicateurs de compilation afin d'éviter les problèmes de performances, par exemple si les assertions LLVM sont activées.
Comme indiqué sur la liste pgsql-hackers, l'impact du JIT sur les coûts n'est pas encore entièrement compris, une planification minutieuse est donc nécessaire avant d'activer le cluster de fonctionnalités à l'échelle, car les requêtes qui pourraient autrement bénéficier de la compilation peuvent en fait s'exécuter plus lentement. Cependant, JIT peut être activé sur une base par requête.
Pour des informations détaillées sur la mise en œuvre de la compilation JIT, consultez les journaux Git du projet, les Commitfests et le fil de discussion pgsql-hackers.
Joyeux JIT !