PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Ein Überblick über die Just-in-Time-Kompilierung (JIT) für PostgreSQL

In der Vergangenheit hat PostgreSQL Kompilierungsfunktionen in Form von vorzeitiger Kompilierung für PL/pgSQL-Funktionen und in Version 10 eingeführte Kompilierung von Ausdrücken bereitgestellt. Keines davon generiert jedoch Maschinencode.

JIT für SQL wurde vor vielen Jahren diskutiert, und für PostgreSQL ist die Funktion das Ergebnis einer wesentlichen Codeänderung.

Um zu überprüfen, ob die PostgreSQL-Binärdatei mit LLVM-Unterstützung erstellt wurde, verwenden Sie den Befehl pg_configure, um die Kompilier-Flags anzuzeigen, und suchen Sie in der Ausgabe nach –with-llvm. Beispiel für die PGDG-RPM-Distribution:

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'

Warum LLVM-JIT?

Alles begann vor etwa zwei Jahren, wie in Adres Freunds Beitrag erklärt, als sich die Ausdrucksauswertung und Tupelverformung als Hindernisse für die Beschleunigung großer Abfragen herausstellten. Nach dem Hinzufügen der JIT-Implementierung „ist die Ausdrucksauswertung selbst mehr als zehnmal schneller als zuvor“, so Andres. Darüber hinaus erklärt der Q&A-Abschnitt, der seinen Beitrag beendet, die Wahl von LLVM gegenüber anderen Implementierungen.

Während LLVM der ausgewählte Anbieter war, kann der GUC-Parameter jit_provider verwendet werden, um auf einen anderen JIT-Anbieter zu verweisen. Beachten Sie jedoch, dass Inlining-Unterstützung aufgrund der Funktionsweise des Build-Prozesses nur bei Verwendung des LLVM-Anbieters verfügbar ist.

Wann JIT?

Die Dokumentation ist klar:Langlaufende Abfragen, die CPU-gebunden sind, profitieren von der JIT-Kompilierung. Darüber hinaus weisen die Diskussionen in Mailinglisten, auf die in diesem Blog verwiesen wird, darauf hin, dass JIT zu teuer für Abfragen ist, die nur einmal ausgeführt werden.

Im Vergleich zu Programmiersprachen hat PostgreSQL den Vorteil, „zu wissen“, wann JIT erforderlich ist, indem es sich auf den Abfrageplaner verlässt. Zu diesem Zweck wurde eine Reihe von GUC-Parametern eingeführt. Um die Benutzer vor negativen Überraschungen zu schützen, werden die kostenbezogenen Parameter bei der Aktivierung von JIT absichtlich auf angemessen hohe Werte gesetzt. Beachten Sie, dass das Festlegen der JIT-Kostenparameter auf „0“ dazu führt, dass alle Abfragen JIT-kompiliert werden, wodurch alle Ihre Abfragen verlangsamt werden.

Während JIT im Allgemeinen von Vorteil sein kann, gibt es Fälle, in denen die Aktivierung nachteilig sein kann, wie in Commit b9f2d4d3 beschrieben.

Wie JIT?

Wie oben angedeutet, sind die RPM-Binärpakete LLVM-fähig. Damit die JIT-Kompilierung jedoch funktioniert, sind einige zusätzliche Schritte erforderlich:

Nämlich:

[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)

Beachten Sie, dass ich JIT aktiviert habe (das nach der pgsql-hackers-Diskussion, auf die in Commit b9f2d4d3 verwiesen wird, standardmäßig deaktiviert ist). Ich habe auch die Kosten der JIT-Parameter angepasst, wie in der Dokumentation vorgeschlagen.

Der erste Hinweis findet sich in der Datei src/backend/jit/README, auf die in der JIT-Dokumentation verwiesen wird:

Which shared library is loaded is determined by the jit_provider GUC, defaulting to "llvmjit".

Da das RPM-Paket die JIT-Abhängigkeit nicht automatisch einzieht – wie nach intensiven Diskussionen entschieden wurde (siehe den vollständigen Thread) – müssen wir es manuell installieren:

[[email protected] ~]# dnf install postgresql11-llvmjit

Sobald die Installation abgeschlossen ist, können wir sofort 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)

Wir können auch die JIT-Details pro Arbeiter anzeigen:

[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)

Die JIT-Implementierung kann auch die Funktion der parallelen Abfrageausführung nutzen. Lassen Sie uns zur Veranschaulichung zunächst die Parallelisierung deaktivieren:

[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)

Derselbe Befehl mit aktivierten parallelen Abfragen wird in der Hälfte der Zeit ausgeführt:

[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)

Ich fand es interessant, die Ergebnisse der in diesem Beitrag besprochenen Tests während der Anfangsphase der JIT-Implementierung mit der endgültigen Version zu vergleichen. Stellen Sie zunächst sicher, dass die Bedingungen im ursprünglichen Test erfüllt sind, d. h. die Datenbank muss in den Speicher 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
Laden Sie noch heute das Whitepaper PostgreSQL-Verwaltung und -Automatisierung mit ClusterControl herunterErfahren Sie, was Sie wissen müssen, um PostgreSQL bereitzustellen, zu überwachen, zu verwalten und zu skalierenLaden Sie das Whitepaper herunter

Führen Sie die Tests mit deaktiviertem JIT aus:

[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)

Führen Sie als Nächstes die Tests mit aktiviertem JIT aus:

[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)

Das ist eine Beschleunigung von etwa 25 % für den ersten Testfall und 40 % für den zweiten!

Schließlich ist es wichtig, sich daran zu erinnern, dass für vorbereitete Anweisungen die JIT-Kompilierung durchgeführt wird, wenn die Funktion zum ersten Mal ausgeführt wird.

Schlussfolgerung

Standardmäßig ist die JIT-Kompilierung deaktiviert, und bei RPM-basierten Systemen weist das Installationsprogramm nicht darauf hin, dass das JIT-Paket installiert werden muss, das den Bitcode für den Standardanbieter LLVM bereitstellt.

Achten Sie beim Erstellen aus Quellen auf die Compiler-Flags, um Leistungsprobleme zu vermeiden, beispielsweise wenn LLVM-Zusicherungen aktiviert sind.

Wie in der pgsql-hackers-Liste besprochen, sind die JIT-Auswirkungen auf die Kosten noch nicht vollständig verstanden, daher ist eine sorgfältige Planung erforderlich, bevor der Funktionscluster weit aktiviert wird, da Abfragen, die ansonsten von einer Kompilierung profitieren könnten, tatsächlich langsamer ausgeführt werden können. JIT kann jedoch pro Abfrage aktiviert werden.

Ausführliche Informationen zur Implementierung der JIT-Kompilierung finden Sie in den Projekt-Git-Protokollen, den Commitfests und dem pgsql-hackers-Mail-Thread.

Viel Spaß beim JITen!