Z4ppy: Datei blockweise einlesen?

Problem:
Ich habe eine Datei, die weit grösser ist als das Memory Limit, und brauche von ihr die ersten paar Bytes/kB.

Lösung:
Datei blockweise einlesen. Nur wie mach ich das? fopen + fgets geht nicht, da bei fopen ja bereits alles in den Speicher zu laden versucht wird, wie ich in einem Test herausgefunden habe.

MfG Z4ppy

  1. Hello,

    Problem:
    Ich habe eine Datei, die weit grösser ist als das Memory Limit, und brauche von ihr die ersten paar Bytes/kB.

    Lösung:
    Datei blockweise einlesen. Nur wie mach ich das? fopen + fgets geht nicht, da bei fopen ja bereits alles in den Speicher zu laden versucht wird, wie ich in einem Test herausgefunden habe.

    Nein, beim Öffnen mit fopen() werden bei PHP üblicherweise 8kb in den Buffer gelsesen.
    Aber vielleicht hast Du den Fehler ja anserswo gemacht?

    Zeig doch mal Deinen Ansatz.

    Liebe Grüße aus dem schönen Oberharz

    Tom vom Berg

    --
     ☻_
    /▌
    / \ Nur selber lernen macht schlau
    http://bergpost.annerschbarrich.de
    1. Nein, beim Öffnen mit fopen() werden bei PHP üblicherweise 8kb in den Buffer gelsesen.
      Aber vielleicht hast Du den Fehler ja anserswo gemacht?

      Is ja komisch, ich habs grad nochmal neu gemacht und jetz gings... Weiss nicht, was ich vorhin falsch gemacht hab, hatte die Datei bereits wieder gelöscht :P

      MfG Z4ppy

    2. Hallo Tom,

      Winzige Korrektur:

      Nein, beim Öffnen mit fopen() werden bei PHP üblicherweise 8kb in den Buffer gelsesen.

      Nicht einmal das: Ein reines fopen() erst einmal gar nichts aus. Erst wenn man *irgendwas* liest, *dann* wird tatsächlich irgendwas aus der Datei geladen. Und hier verwendet PHP dann tatsächlich 8 KiB-Puffer. (per Default zumindest)

      Viele Grüße,
      Christian

      --
      Mein "Weblog" [RSS]
      Using XSLT to create JSON output (Saxon-B 9.0 for Java)
      »I don't believe you can call yourself a web developer until you've built an app that uses hyperlinks for deletion and have all your data deleted by a search bot.«
                  -- Kommentar bei TDWTF
      1. Hello Christian,

        schön, von Dir zu lesen. Hab Dich schon vermisst. Frohes Neues Jahr.

        Winzige Korrektur:

        Danke. Klar, ist sinnvoller so :-)

        Nein, beim Öffnen mit fopen() werden bei PHP üblicherweise 8kb in den Buffer gelsesen.

        Nicht einmal das: Ein reines fopen() erst einmal gar nichts aus. Erst wenn man *irgendwas* liest, *dann* wird tatsächlich irgendwas aus der Datei geladen. Und hier verwendet PHP dann tatsächlich 8 KiB-Puffer. (per Default zumindest)

        ... wenn sie denn überhaupt vorhanden sind.

        Als ich das schrieb, hatte ich Dich auch im Hinterkopf, weil ich nämlich vergessen habe, wo dieser Default verdrahtet war und ob man ihn (ohne Neukompilation) ändern konnte oder nicht. Weißt Du das auswendig?

        Liebe Grüße aus dem schönen Oberharz

        Tom vom Berg

        --
         ☻_
        /▌
        / \ Nur selber lernen macht schlau
        http://bergpost.annerschbarrich.de
        1. Hallo Tom,

          Frohes Neues Jahr.

          Ebenso.

          weil ich nämlich vergessen habe, wo dieser Default verdrahtet war und ob man ihn (ohne Neukompilation) ändern konnte oder nicht. Weißt Du das auswendig?

          Ich hab mal kurz durch den Source geschaut (ich hab mal PHP 5.3.0 genommen, sollte aber für andere Versionen wie 5.2.x oder 5.3.1 ähnlich sein) und es scheint so, als ob stream->chunk_size für den Read-Buffer verantwortlich wäre, siehe

          main/streams/streams.c:456:             chunk_buf = emalloc(stream->chunk_size);
          main/streams/streams.c:466:                     justread = stream->ops->read(stream, chunk_buf, stream->chunk_size TSRMLS_CC);

          Dann stellt sich die Frage, wie das gesetzt wird. Zum einen gibt's das Default:

          main/streams/streams.c:245:     ret->chunk_size = FG(def_chunk_size);

          Und das kommt daher:

          ext/standard/file.c:174:  FG(def_chunk_size) = PHP_SOCK_CHUNK_SIZE;

          Das ist definiert in:

          main/php_network.h:200:#define PHP_SOCK_CHUNK_SIZE      8192

          (Warum das PHP_SOCK_CHUNK_SIZE heißt, erschließt sich mir auch nicht... FG steht übrigens für "File Globals", halt also mal gar nichts mit Sockets zu tun. ;-))

          Es gibt aber in der internen Streams-API eine Möglichkeit, das zu ändern, nämlich per php_stream_set_option. Die setzt das nämlich, siehe:

          main/streams/streams.c:1182:                            stream->chunk_size = value;

          Dummerweise wird die set_option-Funktion leider nicht exportiert (oder ich sehe gerade den Wald vor lauter Bäumen nicht), d.h. man kann die Puffergröße anscheinend nicht zur Laufzeit ändern... Das heißt: Doch neukompilieren oder zumindest C-Extension schreiben, die diese Funktion aufruft...

          Was nebenbei noch interessant ist: Im PHP-Streams-Code gibt's auch noch eine Konstante CHUNK_SIZE, die ebenfalls auf 8192 gesetzt ist, die aber nichts mit der normalen Puffergröße zu tun hat. Die wird aber nur in _php_stream_copy_to_mem() verwendet, was u.a. von file_get_contents(), file(), getimagesize() im Falle von SWC-Bildern, stream_get_contents(), shell_exec() genutzt wird. In diesen Fällen ist das aber kein wirkliches Problem, weil das intern wieder auf php_stream_read (das mit stream->chunk_size) zurückfällt, was die eigentliche Leseoperation durchführt - mit einem größeren Puffer (und das, was hinterher nur im Speicher herumkopieren wird, ist auch bei Blöcken von nur 8 KiB noch verdammt schnell).

          Viele Grüße,
          Christian

          --
          Mein "Weblog" [RSS]
          Using XSLT to create JSON output (Saxon-B 9.0 for Java)
          »I don't believe you can call yourself a web developer until you've built an app that uses hyperlinks for deletion and have all your data deleted by a search bot.«
                      -- Kommentar bei TDWTF
          1. Hello Christian,

            herzlichen Dank für die ausführliche Recherche
            Die werde ich mir jetzt mal leiber gleich in meine Sammlung kopieren.

            8kB sind ja heutzutage auch nicht mehr viel.
            Wenn ich da an die Speicherprobleme unter dem guten alten DOS denke, wo schon 256Byte pro File zum Albtraum werden konnten...

            Liebe Grüße aus dem schönen Oberharz

            Tom vom Berg

            --
             ☻_
            /▌
            / \ Nur selber lernen macht schlau
            http://bergpost.annerschbarrich.de
          2. Hallo Christian.

            Welchen Grund kann es haben, dass nicht BUFSIZ genutzt wird, das ja m.W. die für ein Betriebssystem optimale Puffergröße sein sollte?

            (Lustigerweise ist es auf meinem MacBook 1024, während es bei Ubuntu die üblichen 8192 sind.)

            Servus,
            Flo

            1. Hallo Flo,

              Welchen Grund kann es haben, dass nicht BUFSIZ genutzt wird, das ja m.W. die für ein Betriebssystem optimale Puffergröße sein sollte?

              BUFSIZ ist die Größe von stdio.h-Puffern, es steht nirgends geschrieben, dass die optimal sein muss...

              Außerdem: Eine Konstante kann sowieso *nie* die optimale Puffergröße sein: Zum einen ist die praktisch gar nicht betriebssystemabhängig. Zum anderen hängt sie von Faktoren ab, die zur Compilezeit gar nicht bekannt sein können:

              * Verwendete Hardware

              Wenn man z.B. von DVD liest, braucht man eine völlig andere
                   Puffergröße als wenn man von Festplatte liest. Und die optimale
                   Puffergröße von Festplatten hängt vom Anschlusstyp, von der
                   Geschwindigkeit der Festplatte, von der Sektorgröße, etc. ab
                   (und das auf nichttriviale Weise).

              * Verwendetes Dateisystem (sowohl Typ als auch konkrete Parameter)

              Je nachdem, welches Dateisystem man einsetzt und welche Parameter
                   eingestellt sind ist die beste Puffergröße auch unterschiedlich.

              * Verwendungszweck in der Applikation

              Wenn die Applikation nur kleine Dateien auslesen will, ist ein
                   kleinerer Puffer besser geeignet, sonst müssen nämlich immer relativ
                   große Speicherbereiche für die Puffer alloziert werden, die selten
                   vollständig genutzt werden. Außerdem kann der Speicherverbrauch
                   unnötig in die Höhe gehen, wenn man zu große Puffer für sehr kleine
                   Dateien verwendet.

              Daher: Eine optimale Puffergröße kann man nicht einfach zur Compilezeit angeben. Viele Programme, bei denen die Puffergröße nenneswert die Performance beeinflusst, bieten daher entweder eine Option an, sie zu ändern (das Programm dd z.B.) oder versuchen, die optimale Größe zur Laufzeit zu ermitteln (z.B. indem sie in der Anfangszeit mit einer Defaultpuffergröße anfangen und die leicht variieren, um zum Optimum zu kommen).

              Für die meisten Zwecke, für die man PHP einsetzt, sind 8 KiB aber ein guter Kompromiss, ich vermute, deswegen wird diese Zahl verwendet. Die Performanceeinbußen bei PHP liegen in der Regel an anderer Stelle.

              Viele Grüße,
              Christian

              --
              Mein "Weblog" [RSS]
              Using XSLT to create JSON output (Saxon-B 9.0 for Java)
              »I don't believe you can call yourself a web developer until you've built an app that uses hyperlinks for deletion and have all your data deleted by a search bot.«
                          -- Kommentar bei TDWTF
              1. Hallo Christian.

                Vielen Dank!

                Servus,
                Flo

  2. Hi,

    Datei blockweise einlesen. Nur wie mach ich das? fopen + fgets geht nicht, da bei fopen ja bereits alles in den Speicher zu laden versucht wird,

    Das wäre mir neu.

    wie ich in einem Test herausgefunden habe.

    Wie sieht dein Testaufbau aus, und wie das Ergebnis?

    MfG ChrisB

    --
    “Whoever best describes the problem is the person most likely to solve the problem.” [Dan Roam]