PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Przegląd kompilacji Just-in-Time (JIT) dla PostgreSQL

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!


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Postgres - jak zwrócić wiersze z liczbą 0 za brakujące dane?

  2. Jak wykonać zapytanie o wartości null w polu typu json postgresql?

  3. Błąd wartości podczas importowania danych do tabeli postgres za pomocą psycopg2

  4. Warunek SQL LIKE do sprawdzenia liczby całkowitej?

  5. Jak zachować dane w zadokowanej bazie danych postgres przy użyciu woluminów?