Marko: Garbage Collection zur Script Laufzeit ?

Hallo,

gibt es in PHP eine Möglichkeit Objekte vor dem Scriptende aus dem Speicher zu
entfernen ?
Ich stosse gerade auf das Problem, dass ein Script eine komplette, ziemlich grosse Webseite aus einer Datenbank generiert. Dieser Prozess läuft sehr selten, also ist Zeit und Performance kein Problem. Aber mittlerweile musste ich das Memory Limit auf 30MB hochschrauben, weil während der Generierung sich immer mehr Müll im Speicher sammelt.
Eine Aufteilung in mehrere Schritte, die durch getrennte Requests ausgelöst werden wäre zwar möglich, aber umständlich.
Falls PHP4 nicht damit dienen kann, wie sieht es denn in PHP5 mit Garbage Collection aus ?

Danke und Gruss

Marko

  1. Halihallo Marko

    gibt es in PHP eine Möglichkeit Objekte vor dem Scriptende aus dem Speicher zu
    entfernen ?

    Ja. http://www.php.net/unset gibt die Variable für Garbage
    Collecting frei.

    Viele Grüsse

    Philipp

    1. Halihallo Marko

      Ja. http://www.php.net/unset gibt die Variable für Garbage
      Collecting frei.

      Äm. Sie wird zwar für das Collecting freigegeben, aber das heisst
      noch lange nicht, dass der Speicher an das OS zurückgegeben wird.
      Ich weiss nicht genau, wie PHP dies intern handhabt, aber PHP wird
      wohl auch ein internes Memory Management haben und hält sich den
      Speicher vielleicht noch zurück...

      BTW: Überschreibst du einen Variableninhalt, der nirgendwo sonst
      referenziert ist (über eine Referenz), wird der Inhalt der Variablen
      auch für das Garbage Collecting freigegeben. Nur die Variable selber
      ist immer noch definiert (mit dem neuen Wert).

      Viele Grüsse

      Philipp

    2. Hi!

      gibt es in PHP eine Möglichkeit Objekte vor dem Scriptende aus dem Speicher zu
      entfernen ?

      Lies mal was zu "Reference-Counting" durch: http://www.zend.com/zend/art/ref-count.php

      Du musst vor allem darauf achten - gerade bei großen Objekten, dass Du die Objekte möglichst explizit als Referenz übergibst, so dass die großen Objekte nicht mehrfach im Speicher existieren.

      Du kannst Variablen auch selber dereferenzieren, am besten mit

      $var = NULL;

      Ja. http://www.php.net/unset gibt die Variable für Garbage
      Collecting frei.

      ich sags ja - PHP`ler, da kannste Dich gegen wehren oder nicht ;-)

      Grüße
      Andreas

      1. Halihallo Andreas bzw. eher Marko

        gibt es in PHP eine Möglichkeit Objekte vor dem Scriptende aus dem Speicher zu
        entfernen ?

        Lies mal was zu "Reference-Counting" durch: http://www.zend.com/zend/art/ref-count.php

        Schön, dass PHP das "jetzt" auch kann :-)

        Du musst vor allem darauf achten - gerade bei großen Objekten, dass Du die Objekte möglichst explizit als Referenz übergibst, so dass die großen Objekte nicht mehrfach im Speicher existieren.

        Jep. Nur:   (@Marko)

        <?php
          $t3 = Array(content => 'test');
          $t = Array( name => $t3 );
          unset($t3);
          var_dump($t);
        ?>

        Tja, auch wenn das "array" mit content=>'test' gelöscht wird
        (genauer ist es eben nur die Variable $t3, der Speicher vom Array
        ist davon weitgehend unbeeinflusst), erscheint es noch bei var_dump.
        Warum? - Nun, $t3 erhöht den reference counter (des Arrays!) auf 1,
        die Referenz name im "Array $t" auf 2. Über unset($t3) oder $t3=NULL
        wird der reference counter des ersten Arrays auf 1  decrementiert
        und somit ist das Array (nicht die Variable $t3) immer noch im
        Speicher. Um wirklich für den Garbage Collector zu arbeiten,
        müsste man also auch $t löschen, sodass auch $t3 freigegeben wird...

        Mit anderen Worten: Alle Referenzen auf eine (anonyme!) Datenstruktur
        müssen (entweder Rekursiv oder direkt) gelöscht werden. Merke: Eine
        Variable ist ebenfalls eine Referenz auf eine anonyme Datenstruktur.
        z.B. zeigt bei '$t="hello"', die Variable $t auf eine Instanz der
        Datenstruktur String, welche mit 'hello' "gefüllt" ist und somit
        bereits einen reference counter von 1 aufweist.

        Huch, hoffe das war in etwa verständlich :-)
        Aber lieber doch Andreas verlinktes Dokument lesen :-)

        Ja. http://www.php.net/unset gibt die Variable für Garbage
        Collecting frei.

        ich sags ja - PHP`ler, da kannste Dich gegen wehren oder nicht ;-)

        We are the PHP'ler. Resistence is futile.
        Until you speak Perl, I suppose! :-)

        Viele Grüsse

        Philipp

  2. Hello,

    bekannte Versäumnisse sind

    • Resultsets von Queries nach Gebrauch nicht freigegebn
    • verlorene Handles von fopen(), speziell in Schleifen
        also immer fclose(), wenn die Datei nicht mehr benötigt wird
    • nicht wieder freigegebene Variablen, speziell Hilfsarrays.
        hier greift in Funktionen glücklicherweise das Scope für die Vars

    Bei PHP 4.x werden leider auch immer noch diese Supervariablen $HTTP_... angelegt. Wenn man also nur die neuen benutzt, was ich empfehle, sollte man die anderen gleich am Scriptanfang freigeben. Verhindern kann man die Generierung leider erst in einer neueren Version (ich galube ab 5.x).

    Müsstest mal schauen, ob auch immer noch $HTTP_SESSION_VARS angelegt wird. Das wird dann den meisten Platz fressen.

    Liebe Grüße aus http://www.braunschweig.de

    Tom

    --
    Fortschritt entsteht nur durch die Auseinandersetzung der Kreativen
  3. Vielen Dank für die Antworten erstmal, ich weiss jetzt wo ich suchen muss. Der durchschlagende Erfolg blieb mir zwar noch versagt, aber ich muss wohl das System auf Speicherfresser durchforsten. Da rächt sich, wenn man sich unter PHP noch nie Gedanken drüber gemacht hat.
    Noch eine Frage, gibt es für PHP Werkzeuge, mit denen man untersuchen kann, welche Objekte wieviel Speicher verwenden ?

    Gruss

    Marko

    1. Hallo!

      Vielen Dank für die Antworten erstmal, ich weiss jetzt wo ich suchen muss. Der durchschlagende Erfolg blieb mir zwar noch versagt, aber ich muss wohl das System auf Speicherfresser durchforsten. Da rächt sich, wenn man sich unter PHP noch nie Gedanken drüber gemacht hat.
      Noch eine Frage, gibt es für PHP Werkzeuge, mit denen man untersuchen kann, welche Objekte wieviel Speicher verwenden ?

      Es kommt sehr auf den Quellcode an. Du solltest möglichst modular vorgehen, das heißt entsprechende Methoden/Objekte verwenden, und dabei nicht alles auf einmal machen. Wenn Du sagst Du generierst eine Webseite, drängt sich mir die Vermutung auf dass Du alle Elemente der Webseite erst im Speicher erzeugst, und am Ende auf die Platte schreibst. Du solltest nach Möglichkeit besser sequentiell vorgehen, das heißt wenn möglich Datei für Datei erzeugen und direkt auf die Platte schreiben, und dann die Resourcen freigeben, indem Du dieselben Variablen... verwendest, z.B. in einer Schleife (in dem Fall werden die nicht mehr benötigten Resourcen bei jedem Schleifendurchlauf freigegeben).
      Dann übergebe gerade Objekte und große Arrays nach Möglichkeit immer als Referenz, wie gesagt.
      Du solltest den von mir verlinkten Artikel genau lesen und verstehen, und dann Dein Script entsprechend umbauen indem Du das "Reference-Counting" und Aliase?!(Referenzen -> http://de3.php.net/manual/en/language.references.php) ausnutzt.

      In gewissen Grenzen können Dir die Funktion http://de3.php.net/memory-get-usage und die Extension http://xdebug.org/ helfen speicherintensive Bereiche und Funktionen zu finden.

      Grüße
      Andreas

      1. Hallo Andreas,

        das System ist voll Objektorientiert und modular aufgebaut. Es wird auch Seite für Seite erzeugt. Ich erzeuge die Dynamischen Seiten dabei sequentiell, puffere die Bildschirmausgabe und schreibe sie in Dateien.
        Das Problem liegt wohl in der Objektübergabe, ich übergebe bisher nicht mit Referenzen. Wobei ich noch nicht ganz verstehe, warum der Speicher nicht freigegeben wird. Nach der Generierung einer Seite müssten eigentlich alle Referenzen gelöscht sein.
        Möglicherweise hängt es auch an der DOM Extension, die ja noch beta ist, die ich aber ziemlich stark benutze.
        Ich werde mich mal genau in das reference-counting einlesen, aber vielleicht ist es am besten ich schraube jetzt erstmal den Speicher hoch, und warte auf die 5er PHP. Wahrscheinlich muss ich das System früher oder später eh portieren, und mit PHP 5 werden vielleicht einige Problem von selbst verschwinden (Objektübergabe als Referenz, und eine stabile DOM Implementierung).

        Danke und Gruss

        Marko

        1. Hi!

          das System ist voll Objektorientiert und modular aufgebaut. Es wird auch Seite für Seite erzeugt. Ich erzeuge die Dynamischen Seiten dabei sequentiell, puffere die Bildschirmausgabe und schreibe sie in Dateien.

          Hm. Wie genau machst Du das?
          Rufst Du nach jeder Datei ob_end_clean() auf?
          Verwendest Du immer denselben Variablennamen für die Objekte?

          Ich kann mir nicht vorstellen dass es an Referenzen liegt, udn es wird vermutlich nicht durch PHP5 behoben sein. An irgendeiner Stelle erzeugst Du immer neue Objekte, oder mehr Arrays, einen Array mit neuen Objekten oder schreibst immer mehr in den Ausgabepuffer... irgendsowas.

          Sonst schreibe mal den Inhalt von $GLOBALS am Ende in eine Datei und guck wie groß die ist.

          $fp = fopen ('globals.txt','w');
          fwrite($fp,var_export($GLOBALS, TRUE));
          fclose($fp);

          Wenn die Datei riesig ist musst Du nur suchen wo da sinnlose Daten sind und dann das Script entsprechend ändern, wenn die Datei nicht besonders groß ist, würde ich auf den Ausgabe-Puffer tippen, vielleicht verwendest Du ob_clean(), was vielleicht irgendwelche Daten im Speicher lässt. Vielleicht versuche mal am Ende der Datei:

          $i=0;
          while (ob_end_clean()) {
            $i++;
          }

          $fp = fopen ('ob.txt','w');
          fwrite($fp,$i);
          fclose($fp);

          Was steht dann in der ob.txt für eine Zahl?

          Grüße
          Andreas

          1. Hallo Andreas,

            Rufst Du nach jeder Datei ob_end_clean() auf?

            Ja, das wird in einer eigenen Klasse gemacht. Die Klasse habe ich schon in 3 anderen Projekten verwendet, daran liegt s nicht. Ich habe auch die Zählschliefe getestet, die Du vorgeschlagen hast, das Ergebnis ist 0.

            Verwendest Du immer denselben Variablennamen für die Objekte?

            Eigentlich schon, der Generator sitzt nur vor einem CMS System, das die Dateien erzeugt, für Verwaltungszwecke und bei der Entwicklung wird immer direkt das CMS aufgerufen. Der Generator simuliert mit einem "include" für jede Datei den Aufruf.
            An diesem include in der Schleife kann es eigentlich auch nicht liegen, hab gerade nachgeschaut, es wird nur sehr wenig Code eingebunden, und die Variablen werden dann wiederverwendet. Es ist ein zentraler Controller der ein Serviceobjekt instantiert, eine Methode aufruft, und das Objekt am Ende wieder freigibt.

            Globals habe ich auch gecheckt, steht nichts weltbewegendes drin. Ich denke es liegt doch daran, dass entweder Objekte überleben, die das nicht sollen, oder wahrscheinlicher die DOM Bäume werden nicht freigegeben.Vielleicht ist auch einfach das Problem, dass bei Bedarf mit require_once neuer Code hinzugelinkt wird, bei der Generierung werden nach und nach alle Klassen des Systems eingebunden, das sind schon so ca. 30 Klassen, vielleicht ist das einfach ne Menge Code.

            Gruss

            Marko

            1. Hi!

              Eigentlich schon, der Generator sitzt nur vor einem CMS System, das die Dateien erzeugt, für Verwaltungszwecke und bei der Entwicklung wird immer direkt das CMS aufgerufen. Der Generator simuliert mit einem "include" für jede Datei den Aufruf.

              Naja, daran könnte es durchaus liegen. Bei include bringt das Freigeben  nichts, da wird das Scipt um jede eingebundene Datei immer weiter vergrößert.

              Mach mal vor dem jedem include sowas:

              $GLOBALS['filesize'] += filesize($filename);

              und ganz am Ende

              echo $GLOBALS['filesize'] .' bytes';

              Du solltest statt include() vielleicht mal

              echo file_get_contents() verwenden, wenn das denn geht. Auch wenn das dann am Ende falsche Dateien liefert, würde ich es trotzdem einmal ürobieren, um zu sehen ob es überhaupt an include() liegt, was ich vermute.

              Grüße
              Andreas

              1. Hi,

                die Zählung ergibt 123525 also nur 120 Kb. echo file_get_contents() geht nicht, da der Code ja ausgeführt werden muss.

                Gruss

                Marko

                1. Hi!

                  die Zählung ergibt 123525 also nur 120 Kb. echo file_get_contents() geht nicht, da der Code ja ausgeführt werden muss.

                  Ja, aber muss das den ausgeführt werden damit der Generator funktioniert?  Ist ja total egal wenn in den erzeugten Dateien am Ende Qutsch steht, nur mal um zu testen.
                  Naja, ohne Kenntnis des kompletten Quellcodes weiß ich dann auch nicht mehr weiter, ich würde wie gesagt mal an mehreren Stellen den Speicherverbrauch testen, am besten mal xdebug installieren.
                  Oder beschneide das Script immer weiter in seiner Funkionalität, bis Du die Stelle findest die für den wahnsinnigen Speicherverbrauch verantwortlich ist. Und log alles mögliche mal mit...

                  Grüße
                  Andreas

  4. Halihallo Marko

    Ich stosse gerade auf das Problem, dass ein Script eine komplette, ziemlich grosse Webseite aus einer Datenbank generiert. Dieser Prozess läuft sehr selten, also ist Zeit und Performance kein Problem. Aber mittlerweile musste ich das Memory Limit auf 30MB hochschrauben, weil während der Generierung sich immer mehr Müll im Speicher sammelt.

    Was passiert, wenn du das Memory Limit auf 10MB ansetzt? -
    Fehlermeldung?

    Denn: PHP konsumiert soviel, wie es darf, setzt du das Limit auf 30MB
    scheut sich PHP nicht davor, auch 30MB zu benutzen. Setzt du es
    jedoch auf 10MB, muss es nichtverwendeten Speicher an das System
    zurückgeben bzw. diesen für anderes wieder gebrauchen.
    Es ist ggf. genau falsch das Memory Limit höher anzusetzen, teste
    mal.

    Zudem: Wieviel Speicher braucht ein kleines "Hello World - PHP-
    Script"? - Du wirst feststellen, dass 4GL Sprachen wie PHP und Perl
    gigantischen "Overhead" produzieren.

    Viele Grüsse

    Philipp

    1. Hallo Philipp,

      Was passiert, wenn du das Memory Limit auf 10MB ansetzt? -
      Fehlermeldung?

      ja klar, dadurch bin ich doch überhaupt erst auf das Problem gestossen.

      Gruss

      Marko