Datei blockweise einlesen?
Z4ppy
- php
0 Tom0 Z4ppy0 Christian Seiler0 Tom2 Christian Seiler
0 ChrisB
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
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
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
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
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
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
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
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
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
Hallo Christian.
Vielen Dank!
Servus,
Flo
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