PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


Archive for the ‘Garbage Collector’ tag

Performanceboost durch temporäres Abschalten des PHP Garbage Collectors

without comments

Auf der Suche nach einfachen Möglichkeiten, die Performance eines CPU-bound PHP-Scripts zu steigern, fiel mir wieder ein, wie der Composer Installer einen drastischen Performanceboost hinbekommen hat. Selbst ausprobiert, und siehe da: Statt 7 Sekunden Laufzeit nur noch 2,7 Sekunden. Whao!

Wie macht man sowas mit nur 1-3 Zeilenänderungen?

Garbage Collection

Ein paar kurze Worte zum Garbage Collector. Am Ende dieses Blogartikels sind ein paar Links für diejenigen, die mehr wissen wollen. Ich versuche es einfach darzustellen.

Der Garbage Collector läuft im Hintergrund eines PHP-Scripts in der PHP-Engine, seine Aufgabe ist es, ungenutzten, nicht mehr gebrauchten Arbeitsspeicher wieder frei zu machen. Dazu durchläuft er alle Variablen und Datenstrukturen, um zu schauen ob sie noch gebraucht werden. Wenn ein Speicherbereich noch gebraucht wird, dann gibt es eine Referenz darauf (eine Variable im einfachsten Fall). Er findet also alle Speicherbereiche, zu denen es keine Variable mehr gibt, und gibt den Speicher wieder frei.

Das ist nur der einfachste Fall, sehr simpel erklärt. Im Detail muss man auch über Objekte nachdenken, Ressourcen, zyklische Referenzen (wenn X Variablen sich „im Kreis“ referenzieren), und so weiter. Es ist sogar so, dass es beim Garbage Collector von PHP nur um die zyklischen Referenzen geht, normale Variablen werden beim Verlassen eines Scopes (einer Funktion/Methode) schon wieder freigegeben, problematisch sind aber die komplizierteren Referenzen zwischen Objekten und mehreren Variablen, die gegenseitig auf sich zeigen.

Garbage Collection gibt es in allen Programmiersprachen, bei denen der Programmierer sich nicht manuell selbst drum kümmern muss/soll, Speicher wieder freizugeben. In C beispielsweise muss sich der Programmierer selbst drum kümmern, und das geht häufig schief und führt zu Memory Leaks, Sicherheitsproblemen, „double-free“ Fehlern und so weiter. Deshalb nehmen moderne/sicherere Hochsprachen einem Programmierer diese Arbeit ab, die aller meisten modernen Programmiersprachen haben Garbage Collectoren.

Garbage Collectoren laufen also im Hintergrund mit, und versuchen „ab und zu“ Speicher freizubekommen. Dabei durchsucht er alle existierenden Variablen/Datenstrukturen, ob eine davon verworfen werden kann, weil sie nicht mehr gebraucht wird. Hat man sehr viele (Millionen) Objekte, dauert solch ein Durchlauf entsprechend lang, und im schlimmsten Fall ist das Ergebnis: Alle Speicherbereiche werden noch benötigt, es gibt nichts zu tun.

Wie gesagt, recht vereinfacht, in den letzten Jahren gab es sehr viele verbesserte Garbage Collectoren, die nicht immer alle Datenstrukturen durchsuchen, und deren Laufzeit beschränkt wird wenn es zu viel zu durchsuchen gibt, usw. Stichworte: „Serial collector“, „Parallel collector“, „Concurrent Mark Sweep (CMS) Collector“, „Generational Garbage Collector“.

Garbage Collector temporär abschalten

Wie kommt man auf die Idee, die Garbage Collection temporär zu deaktivieren? Es gibt einen bekannten Fall im PHP-Universum: Der Composer Installer. Er war berühmt-berüchtigt für seine Langsamkeit, da die Berechnung der Dependency-Bäume sehr langsam war. Durch eine einzige Zeilenänderung (gc_disable();) konnte der Installer um 30-90% beschleunigt werden.
https://tideways.com/profiler/blog/how-to-optimize-the-php-garbage-collector-usage-to-improve-memory-and-performance
https://blog.blackfire.io/performance-impact-of-the-php-garbage-collector.html

Wenn man als Programmierer weiß, dass in einem bestimmten Code-Block, oder einer Methode, oder gar einem ganzen PHP-Script, der Garbage Collector nur unnötig versucht, Speicher frei zu machen, dann kann man ihn deaktivieren, um der Engine die unnötige Arbeit zu ersparen. Bei PHP-Scripten, die sehr viel CPU benötigen, und die mit vielen Objekten und Speicher arbeiten, kann das Wunder bewirken.

Erst heute morgen habe ich es beim DomPDF Projekt ausprobiert. Mit DomPDF kann man sehr einfach aus (einfachem) HTML ein PDF rendern. Doch DomPDF ist recht langsam bei großen, mehrseitigen Dokumenten, da dauert es gern mal mehrere Hundert Millisekunden oder gar Sekunden. Da gerade in einem Issue über Performance diskutiert wurde, habe ich in der render() Methode den Garbage Collector am Anfang deaktiviert, und am Ende wieder aktiviert. Ein (künstliches) Test-HTML-Dokument benötigt nur noch 2.7 Sekunden statt 7 Sekunden, also eine ähnliche Performancesteigerung wie beim Composer Installer. Und das mit nur 3 Zeilen Code.

$ git diff
diff --git a/src/Dompdf.php b/src/Dompdf.php
index d031938..df37e8d 100644
--- a/src/Dompdf.php
+++ b/src/Dompdf.php
@@ -701,6 +701,8 @@ class Dompdf
      */
     public function render()
     {
+        gc_disable();
+
         $this->saveLocale();
         $options = $this->options;

@@ -864,6 +866,9 @@ class Dompdf
         }

         $this->restoreLocale();
+
+        gc_enable();
+        gc_collect_cycles();
     }

Vorher:

$ php test_performance.php break_tag 10000
Profiling PDF generation using 'break_tag' over 10000 iterations.
STEP          MEMORY USE    PEAK MEMORY   EXECUTION TIME
Start         2,528,280     2,883,608     -
Load          2,553,104     2,883,608     0.0082650184631348
Render        36,158,096    49,725,776    6.9973478317261     <======
Output        36,168,776    49,725,776    0.00060415267944336
End           36,169,232

Nachher:

$ php test_performance.php break_tag 10000
Profiling PDF generation using 'break_tag' over 10000 iterations.
STEP          MEMORY USE    PEAK MEMORY   EXECUTION TIME
Start         2,528,584     2,883,912     -
Load          2,553,408     2,883,912     0.0081119537353516
Render        36,158,432    49,719,360    2.7755401134491    <======
Output        36,169,112    49,719,360    0.00069785118103027
End           36,169,568

Es kommt, wie häufig, auf den Anwendungsfall an, ob dieses temporäre Abschalten des Garbage Collectors etwas bringt oder nicht. Man sollte dabei beachten, dass man dadurch eventuell den Speicherverbrauch erhöht, da eben kein Speicher mehr freigemacht wird wenn der Garbage Collector abgeschaltet ist. Man sollte es also nur nutzen, wenn man dadurch nicht in Speicherprobleme gerät.

Ob man den Garbage Collector am Ende wieder aktiviert, oder bis zum Scriptende ausgeschaltet lässt, kommt drauf an: Wenn man das Script komplett unter der eigenen Kontrolle hat, und es sich nicht um einen langlaufenden Daemon handelt, dann kann man ihn eventuell deaktiviert lassen. Im Falle von DomPDF ist es aber eine Library, die eventuell in einem Daemon genutzt wird, man sollte also unbedingt am Ende den Garbage Collector wieder aktivieren, sonst wundert sich der Nutzer der Library über einen sehr hohen Speicherverbrauch in seinem Script. Deshalb wird in DomPDF der Garbage Collector wieder aktiviert, der Composer Installer jedoch lässt ihn bis zum Ende ausgeschaltet.

Probiert es mal aus in euren langsamen Scripten, die CPU-bound sind und mit vielen Objekten/Referenzen etc. arbeiten. Vielleicht habt ihr dann ähnliche Erfolgserlebnisse 🙂

Und hier die versprochenen Links zu Details des (PHP) Garbage Collectors:

https://blog.ircmaxell.com/2014/12/what-about-garbage.html
https://react-etc.net/entry/improvements-to-garbage-collection-gc-php-7-3-boosts-performance-in-benchmark
https://www.sitepoint.com/better-understanding-phps-garbage-collection/
http://php.net/manual/de/features.gc.performance-considerations.php

Written by Michael Kliewe

Oktober 12th, 2018 at 10:46 am