Historisch gezien bood PostgreSQL compilatiefuncties in de vorm van een compilatie van tevoren voor PL/pgSQL-functies en in versie 10 geïntroduceerde compilatie van expressies. Geen van deze genereert echter machinecode.
JIT voor SQL werd vele jaren geleden besproken en voor PostgreSQL is de functie het resultaat van een substantiële codewijziging.
Om te controleren of PostgreSQL binair is gebouwd met LLVM-ondersteuning, gebruikt u de opdracht pg_configure om de compileervlaggen weer te geven en zoekt u naar –with-llvm in de uitvoer. Voorbeeld voor de PGDG RPM-verdeling:
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'
Waarom LLVM JIT?
Het begon allemaal ongeveer twee jaar geleden, zoals uitgelegd in de post van Adres Freund, toen expressie-evaluatie en tupelvervorming de wegversperringen bleken te zijn bij het versnellen van grote zoekopdrachten. Na het toevoegen van de JIT-implementatie "is de evaluatie van expressies zelf meer dan tien keer sneller dan voorheen" in de woorden van Andres. Verder legt de Q&A-sectie die zijn bericht beëindigt de keuze van LLVM boven andere implementaties uit.
Terwijl LLVM de gekozen provider was, kan de GUC-parameter jit_provider worden gebruikt om naar een andere JIT-provider te verwijzen. Houd er echter rekening mee dat inlining-ondersteuning alleen beschikbaar is bij gebruik van de LLVM-provider, vanwege de manier waarop het bouwproces werkt.
Wanneer JIT?
De documentatie is duidelijk:langlopende query's die CPU-gebonden zijn, zullen baat hebben bij JIT-compilatie. Bovendien wijzen de discussies op de mailinglijst waarnaar in deze blog wordt verwezen, erop dat JIT te duur is voor zoekopdrachten die maar één keer worden uitgevoerd.
In vergelijking met programmeertalen heeft PostgreSQL het voordeel dat het "wett" wanneer JIT moet worden gebruikt, door te vertrouwen op de queryplanner. Daartoe werden een aantal GUC-parameters ingevoerd. Om gebruikers te beschermen tegen negatieve verrassingen bij het inschakelen van JIT, zijn de kostengerelateerde parameters opzettelijk ingesteld op redelijk hoge waarden. Houd er rekening mee dat het instellen van de JIT-kostenparameters op '0' ervoor zorgt dat alle zoekopdrachten JIT-gecompileerd worden en als resultaat al uw zoekopdrachten vertragen.
Hoewel JIT over het algemeen gunstig kan zijn, zijn er gevallen waarin het inschakelen ervan nadelig kan zijn, zoals besproken in commit b9f2d4d3.
Hoe JIT doen?
Zoals hierboven vermeld, zijn de RPM binaire pakketten LLVM-enabled. Om de JIT-compilatie echter te laten werken, zijn een paar extra stappen vereist:
Te weten:
[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)
Merk op dat ik JIT heb ingeschakeld (die standaard is uitgeschakeld na de pgsql-hackers-discussie waarnaar wordt verwezen in commit b9f2d4d3). Ik heb ook de kosten van JIT-parameters aangepast zoals voorgesteld in de documentatie.
De eerste hint is te vinden in het src/backend/jit/README-bestand waarnaar wordt verwezen in de JIT-documentatie:
Which shared library is loaded is determined by the jit_provider GUC, defaulting to "llvmjit".
Aangezien het RPM-pakket de JIT-afhankelijkheid niet automatisch binnenhaalt - zoals werd besloten na intensieve discussies (zie de volledige thread) - moeten we het handmatig installeren:
[[email protected] ~]# dnf install postgresql11-llvmjit
Zodra de installatie is voltooid, kunnen we meteen testen:
[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)
We kunnen ook de JIT-gegevens per werknemer weergeven:
[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)
De JIT-implementatie kan ook profiteren van de functie voor het uitvoeren van parallelle query's. Laten we, om een voorbeeld te geven, eerst parallellisatie uitschakelen:
[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)
Dezelfde opdracht met ingeschakelde parallelle zoekopdrachten wordt in de helft van de tijd voltooid:
[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)
Ik vond het interessant om de resultaten van de tests die in dit bericht worden besproken, tijdens de beginfase van de JIT-implementatie te vergelijken met de definitieve versie. Zorg er eerst voor dat aan de voorwaarden in de originele test is voldaan, d.w.z. de database moet in het geheugen passen:
[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
Download de whitepaper vandaag PostgreSQL-beheer en -automatisering met ClusterControlLees wat u moet weten om PostgreSQL te implementeren, bewaken, beheren en schalenDownload de whitepaper Voer de tests uit met JIT uitgeschakeld:
[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)
Voer vervolgens de tests uit met JIT ingeschakeld:
[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)
Dat is een versnelling van ongeveer 25% voor de eerste testcase en 40% voor de tweede!
Ten slotte is het belangrijk om te onthouden dat voor voorbereide instructies de JIT-compilatie wordt uitgevoerd wanneer de functie voor het eerst wordt uitgevoerd.
Conclusie
Standaard is JIT-compilatie uitgeschakeld, en voor op RPM gebaseerde systemen zal het installatieprogramma geen hints geven over de noodzaak om het JIT-pakket te installeren dat de bitcode levert voor de standaardprovider LLVM.
Let bij het bouwen van bronnen op de compileervlaggen om prestatieproblemen te voorkomen, bijvoorbeeld als LLVM-bevestigingen zijn ingeschakeld.
Zoals besproken op de pgsql-hackerslijst, wordt de JIT-impact op de kosten nog niet volledig begrepen, dus een zorgvuldige planning is vereist voordat de functiecluster breed wordt ingeschakeld, omdat query's die anders baat zouden hebben bij compilatie, in werkelijkheid langzamer zouden kunnen werken. JIT kan echter per zoekopdracht worden ingeschakeld.
Voor diepgaande informatie over de implementatie van de JIT-compilatie, bekijk de Git-logboeken van het project, de Commitfests en de pgsql-hackers-mailthread.
Veel plezier met JITing!