Historycznie PostgreSQL zapewniał funkcje kompilacji w postaci kompilacji z wyprzedzeniem dla funkcji PL/pgSQL, a wersja 10 wprowadziła kompilację wyrażeń. Żaden z nich nie generuje jednak kodu maszynowego.
JIT dla SQL był omawiany wiele lat temu, a dla PostgreSQL ta funkcja jest wynikiem znacznej zmiany kodu.
Aby sprawdzić, czy plik binarny PostgreSQL został zbudowany z obsługą LLVM, użyj polecenia pg_configure, aby wyświetlić flagi kompilacji i poszukaj –with-llvm w danych wyjściowych. Przykład dla dystrybucji 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'
Dlaczego JIT LLVM?
Wszystko zaczęło się około dwa lata temu, jak wyjaśniono w poście Adres Freund, kiedy ocena wyrażeń i deformacja krotek okazały się przeszkodami w przyspieszaniu dużych zapytań. Po dodaniu implementacji JIT „sama ocena wyrażeń jest ponad dziesięciokrotnie szybsza niż wcześniej”, jak mówi Andres. Ponadto sekcja pytań i odpowiedzi kończąca jego post wyjaśnia wybór LLVM w stosunku do innych implementacji.
Chociaż LLVM był wybranym dostawcą, parametr GUC jit_provider może służyć do wskazywania innego dostawcy JIT. Pamiętaj jednak, że obsługa inline jest dostępna tylko podczas korzystania z dostawcy LLVM, ze względu na sposób działania procesu kompilacji.
Kiedy JIT?
Dokumentacja jest jasna:długo działające zapytania, które są powiązane z procesorem, skorzystają na kompilacji JIT. Ponadto dyskusje na listach dyskusyjnych, do których odwołuje się ten blog, wskazują, że JIT jest zbyt drogi dla zapytań wykonywanych tylko raz.
W porównaniu z językami programowania PostgreSQL ma tę zaletę, że „wiedząc”, kiedy należy JIT, polega na planerze zapytań. W tym celu wprowadzono szereg parametrów GUC. Aby chronić użytkowników przed negatywnymi niespodziankami podczas włączania JIT, parametry związane z kosztami są celowo ustawiane na rozsądnie wysokie wartości. Zwróć uwagę, że ustawienie parametrów kosztu JIT na „0” wymusi kompilację wszystkich zapytań JIT i w rezultacie spowolni wszystkie zapytania.
Chociaż JIT może być ogólnie korzystny, zdarzają się przypadki, w których jego włączenie może być szkodliwe, jak omówiono w commicie b9f2d4d3.
Jak JIT?
Jak wspomniano powyżej, pakiety binarne RPM obsługują LLVM. Jednak, aby kompilacja JIT działała, wymaganych jest kilka dodatkowych kroków:
To znaczy:
[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)
Zauważ, że włączyłem JIT (który jest domyślnie wyłączony po dyskusji pgsql-hackers, do której odwołuje się commit b9f2d4d3). Dostosowałem również koszt parametrów JIT zgodnie z sugestią w dokumentacji.
Pierwsza wskazówka znajduje się w pliku src/backend/jit/README, do którego odwołuje się dokumentacja JIT:
Which shared library is loaded is determined by the jit_provider GUC, defaulting to "llvmjit".
Ponieważ pakiet RPM nie pobiera automatycznie zależności JIT — jak postanowiono po intensywnych dyskusjach (zobacz pełny wątek) — musimy zainstalować go ręcznie:
[[email protected] ~]# dnf install postgresql11-llvmjit
Po zakończeniu instalacji możemy od razu przetestować:
[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)
Możemy również wyświetlić szczegóły JIT na pracownika:
[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)
Implementacja JIT może również korzystać z funkcji równoległego wykonywania zapytań. Dla przykładu, najpierw wyłączmy zrównoleglenie:
[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)
To samo polecenie z włączonymi zapytaniami równoległymi wykonuje się o połowę krócej:
[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)
Ciekawe było porównanie wyników testów omówionych w tym poście, na początkowych etapach implementacji JIT z wersją ostateczną. Najpierw upewnij się, że spełnione są warunki oryginalnego testu, tj. baza danych musi zmieścić się w pamięci:
[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
Pobierz oficjalny dokument już dziś Zarządzanie i automatyzacja PostgreSQL za pomocą ClusterControlDowiedz się, co musisz wiedzieć, aby wdrażać, monitorować, zarządzać i skalować PostgreSQLPobierz oficjalny dokument Uruchom testy z wyłączonym 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)
Następnie uruchom testy z włączonym 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)
Oznacza to przyspieszenie o około 25% dla pierwszego przypadku testowego i 40% dla drugiego!
Na koniec ważne jest, aby pamiętać, że w przypadku przygotowanych instrukcji kompilacja JIT jest wykonywana podczas pierwszego wykonania funkcji.
Wniosek
Domyślnie kompilacja JIT jest wyłączona, a w przypadku systemów opartych na RPM instalator nie podpowie o konieczności zainstalowania pakietu JIT dostarczającego kod bitowy dla domyślnego dostawcy LLVM.
Podczas kompilowania ze źródeł zwracaj uwagę na flagi kompilacji, aby uniknąć problemów z wydajnością, na przykład, jeśli włączone są asercje LLVM.
Jak omówiono na liście hakerów pgsql, wpływ JIT na koszty nie jest jeszcze w pełni zrozumiały, więc przed włączeniem całego klastra funkcji wymagane jest staranne planowanie, ponieważ zapytania, które w przeciwnym razie mogłyby skorzystać z kompilacji, mogą w rzeczywistości działać wolniej. JIT można jednak włączyć na podstawie zapytania.
Aby uzyskać szczegółowe informacje na temat implementacji kompilacji JIT, przejrzyj logi projektu Git, Commitfests i wątek pocztowy pgsql-hackers.
Miłego JITowania!