SplFixedArray & Co: speichereffiziente Datenstrukturen statt PHP-Arrays
AI generated
<?php
8.4
PHP · SplFixedArray · Datenstrukturen · Speicher
SplFixedArray & Co statt PHP-Arrays
Speichereffiziente Datenstrukturen für große Datenmengen

Ein gewöhnliches PHP-Array ist intern eine geordnete Hashtabelle mit erheblichem Overhead pro Element, selbst wenn ausschließlich fortlaufende Integer-Schlüssel verwendet werden. SplFixedArray, SplStack, SplQueue und SplHeap bieten spezialisierte, speichereffiziente Datenstrukturen, die diesen Overhead bei großen, klar strukturierten Datenmengen spürbar reduzieren.

14 Min. Lesezeit SplFixedArray · SplStack · SplHeap PHP 8.x

1. Warum gewöhnliche PHP-Arrays viel Overhead pro Element haben

Ein PHP-Array ist intern keine einfache, zusammenhängende Speicherliste, sondern eine geordnete Hashtabelle, die sowohl beliebige Schlüssel als auch die Einfügereihenfolge verwalten muss. Für jedes einzelne Element speichert diese Struktur zusätzlich zum eigentlichen Wert einen Hash-Bucket-Eintrag, einen Zeiger auf das nächste Element in Einfügereihenfolge und Verwaltungsinformationen für den zugrundeliegenden zend_array. Dieser Overhead liegt je nach PHP-Version und Werttyp häufig bei mehreren Dutzend Bytes zusätzlich pro Element, unabhängig vom eigentlichen Nutzdateninhalt.

Bei einem Array mit wenigen hundert Elementen fällt dieser Overhead praktisch nie auf. Bei Millionen von Elementen, etwa beim Import großer CSV-Dateien, der Verarbeitung von Sensordaten oder dem Aufbau großer numerischer Vektoren für Berechnungen, summiert sich dieser Overhead dagegen zu einem erheblichen, vermeidbaren Speicherverbrauch, der bei einem einfachen Array mit rein sequentiellen Integer-Werten fachlich gar nicht notwendig wäre.

Die Standard PHP Library, kurz SPL, bietet für genau diesen Fall spezialisierte, speichereffiziente Datenstrukturen: SplFixedArray für Arrays mit fester Größe und sequentiellen Integer-Schlüsseln, SplStack, SplQueue und SplDoublyLinkedList für sequentielle Zugriffsmuster ohne wahlfreien Zugriff, sowie SplHeap und SplPriorityQueue für sortierte Verarbeitung. Jede dieser Strukturen verzichtet gezielt auf Funktionalität, die ein gewöhnliches Array bietet, aber im konkreten Anwendungsfall nicht gebraucht wird, und spart dadurch Speicher.

2. SplFixedArray: feste Größe, deutlich weniger Speicher

SplFixedArray ist die direkteste Alternative zu einem gewöhnlichen PHP-Array für den Fall, dass die Anzahl der Elemente von vornherein bekannt ist und ausschließlich fortlaufende Integer-Indizes ab null verwendet werden. Intern verzichtet SplFixedArray vollständig auf die Hashtabellen-Struktur eines gewöhnlichen Arrays und verwendet stattdessen einen echten, zusammenhängenden C-Array-Speicherblock, in dem jedes Element ohne Hash-Bucket-Overhead direkt an seiner berechneten Speicherposition liegt.

Der Preis für diese Effizienz: SplFixedArray unterstützt weder String-Schlüssel noch dynamisches Wachsen ohne explizite setSize()-Aufrufe, und ein Zugriff außerhalb der festgelegten Größe löst eine OutOfRangeException aus, statt stillschweigend ein neues Element anzulegen. Für Anwendungsfälle mit im Voraus bekannter, fester Größe, etwa das Einlesen einer CSV-Datei mit bekannter Zeilenzahl oder das Erzeugen eines numerischen Vektors fester Länge für eine mathematische Berechnung, ist das kein Nachteil, sondern eine willkommene Frühwarnung bei Programmierfehlern.


declare(strict_types=1);

// Regular array: hash table overhead per element, even with sequential int keys
$regularArray = [];
for ($i = 0; $i < 1_000_000; $i++) {
    $regularArray[$i] = $i * 1.5;
}
printf("Regular array: %d bytes\n", memory_get_usage(true));

unset($regularArray);
gc_collect_cycles();

// SplFixedArray: contiguous memory block, no hash bucket overhead
$fixedArray = new SplFixedArray(1_000_000);
for ($i = 0; $i < 1_000_000; $i++) {
    $fixedArray[$i] = $i * 1.5;
}
printf("SplFixedArray: %d bytes\n", memory_get_usage(true));

// Accessing beyond the declared size raises OutOfRangeException,
// catching programming errors instead of silently creating new keys

3. SplStack, SplQueue und SplDoublyLinkedList im Detail

SplDoublyLinkedList bildet eine doppelt verkettete Liste ab, bei der jedes Element nur seinen Wert sowie Zeiger auf Vorgänger und Nachfolger speichert, ohne die zusätzliche Hash-Bucket-Struktur eines gewöhnlichen Arrays. SplStack und SplQueue erben von dieser Basisklasse und spezialisieren sich auf Last-In-First-Out- beziehungsweise First-In-First-Out-Zugriffsmuster, mit entsprechend benannten Methoden wie push(), pop(), enqueue() und dequeue(), die die Absicht im Code deutlich klarer ausdrücken als ein gewöhnliches Array mit array_push() und array_shift().

Ein wichtiger Performance-Unterschied gegenüber einem Array: array_shift() auf einem gewöhnlichen PHP-Array muss alle verbleibenden Elemente um eine Position nach vorne verschieben, was bei großen Arrays zu einer Operation mit linearem Zeitaufwand wird. SplQueue::dequeue() dagegen entfernt lediglich den Kopf der verketteten Liste und passt einen Zeiger an, was unabhängig von der Listengröße in konstanter Zeit geschieht. Bei häufigen Warteschlangen-Operationen auf großen Datenmengen ist dieser Unterschied nicht nur eine Speicherfrage, sondern auch ein spürbarer Performance-Vorteil.


declare(strict_types=1);

/**
 * Process a large batch of jobs using SplQueue instead of a plain array
 * for constant-time dequeue instead of array_shift's linear cost.
 */
final class JobQueue
{
    private SplQueue $queue;

    public function __construct()
    {
        $this->queue = new SplQueue();
    }

    public function enqueue(callable $job): void
    {
        $this->queue->enqueue($job);
    }

    public function processAll(): void
    {
        while (!$this->queue->isEmpty()) {
            $job = $this->queue->dequeue(); // O(1), unlike array_shift()
            $job();
        }
    }
}

4. SplHeap und SplPriorityQueue für sortierte Verarbeitung

SplHeap implementiert einen binären Heap, eine Baumstruktur, die stets in konstanter Zeit Zugriff auf das kleinste oder größte Element bietet, je nachdem, ob eine abgeleitete Klasse SplMinHeap oder SplMaxHeap verwendet wird, beziehungsweise eine eigene compare()-Methode implementiert. Verglichen mit dem naiven Ansatz, ein Array bei jeder Einfügung komplett neu zu sortieren, bietet SplHeap für Einfüge- und Entnahmeoperationen eine logarithmische statt einer linearen oder gar quadratischen Laufzeit.

SplPriorityQueue baut auf demselben Heap-Prinzip auf, erlaubt aber, Werte zusammen mit einer separaten Priorität einzufügen, wobei die Reihenfolge der Entnahme ausschließlich von dieser Priorität bestimmt wird, nicht von der Einfügereihenfolge. Für Anwendungsfälle wie eine Task-Warteschlange mit unterschiedlichen Dringlichkeitsstufen oder eine Dijkstra-Implementierung für kürzeste Wege ist SplPriorityQueue die naheliegende, speichereffiziente Wahl gegenüber einem manuell sortierten Array, das nach jeder Einfügung neu geordnet werden müsste.

5. Praxisbeispiel: Bulk-Import mit SplFixedArray

Ein typischer Anwendungsfall für SplFixedArray ist der Import einer großen CSV-Datei mit bekannter Zeilenzahl, etwa ein Produktkatalog-Export mit mehreren hunderttausend Zeilen, der vollständig im Speicher gehalten werden soll, bevor er in eine Datenbank geschrieben wird. Statt eines gewöhnlichen Arrays, das mit jedem [] =-Zugriff potenziell neu alloziert werden muss, wird die Zielgröße vorab über count() auf der Quelldatei oder eine bekannte Metadaten-Angabe ermittelt und SplFixedArray direkt mit dieser Größe initialisiert.

Dieser Ansatz vermeidet nicht nur den Hashtabellen-Overhead pro Zeile, sondern auch wiederholte interne Reallokationen, die bei einem gewöhnlichen Array auftreten können, wenn dessen intern reservierter Speicherblock während des Befüllens mehrfach vergrößert werden muss. Bei sehr großen Importmengen im Millionenbereich macht diese Kombination aus reduziertem Overhead und vermiedenen Reallokationen einen messbaren Unterschied im Spitzenspeicherverbrauch während des Imports.


declare(strict_types=1);

/**
 * Reads a CSV file with a known row count into a memory-efficient
 * fixed-size structure instead of a dynamically growing array.
 *
 * @param string $path Path to the CSV file.
 * @param int $expectedRows Known number of data rows, excluding the header.
 * @return SplFixedArray<array<int, string>>
 */
function importCsvBulk(string $path, int $expectedRows): SplFixedArray
{
    $rows = new SplFixedArray($expectedRows);
    $handle = fopen($path, 'rb');

    fgetcsv($handle); // skip header row

    $index = 0;
    while (($row = fgetcsv($handle)) !== false && $index < $expectedRows) {
        $rows[$index] = $row;
        $index++;
    }

    fclose($handle);
    return $rows;
}

6. Speicherverbrauch messen: Array gegen SPL-Struktur

Um den tatsächlichen Speichervorteil im eigenen Anwendungsfall zu belegen, genügt ein einfacher Vergleich mit memory_get_peak_usage(true) vor und nach dem Befüllen jeweils eines gewöhnlichen Arrays und der entsprechenden SPL-Struktur mit identischen Testdaten. Wichtig dabei: gc_collect_cycles() zwischen den beiden Messungen aufrufen und, falls möglich, jede Messung in einem separaten PHP-Prozess durchführen, um Verzerrungen durch bereits vom vorherigen Test belegten, aber noch nicht freigegebenen Speicher zu vermeiden.

Als grobe Orientierung liegt der Speichervorteil von SplFixedArray gegenüber einem gewöhnlichen Array mit reinen Integer- oder Float-Werten häufig im Bereich von dreißig bis fünfzig Prozent weniger Speicherverbrauch, abhängig von PHP-Version und den konkret gespeicherten Werttypen. Bei Arrays mit komplexeren, verschachtelten Werten wie Objekten oder Unterarrays fällt der relative Unterschied geringer aus, weil der Speicherbedarf der Werte selbst dann den Anteil des reinen Hashtabellen-Overheads relativ gesehen verkleinert.

7. Wann sich der Umstieg wirklich lohnt

Ein Umstieg auf SplFixedArray oder eine andere SPL-Struktur lohnt sich vor allem bei großen Datenmengen im sechs- bis siebenstelligen Elementbereich, bei denen der Speicherverbrauch tatsächlich zum limitierenden Faktor wird, etwa bei Batch-Jobs, Datenexporten oder numerischen Berechnungen, die vollständig im Arbeitsspeicher gehalten werden. Bei kleineren Datenmengen im niedrigen drei- oder vierstelligen Bereich ist der absolute Speicherunterschied meist vernachlässigbar, während die reduzierte Lesbarkeit und die fehlende Unterstützung gewohnter Array-Funktionen wie array_map() den Code unnötig komplizieren würden.

Ein weiteres, oft übersehenes Kriterium: SPL-Strukturen implementieren zwar Iterator und lassen sich damit in foreach-Schleifen verwenden, aber die reichhaltige Sammlung von Array-Funktionen wie array_filter(), array_map() oder array_reduce() funktioniert nicht direkt auf ihnen. Ein Umweg über iterator_to_array() hebt den Speichervorteil wieder auf, weil dabei intern wieder ein gewöhnliches Array erzeugt wird. Der Umstieg lohnt sich deshalb nur, wenn der Code tatsächlich konsequent mit der spezialisierten Struktur arbeitet, statt sie nur kurzzeitig zu verwenden und dann doch wieder in ein Array zu konvertieren.

8. Grenzen und Kompatibilitätsprobleme der SPL-Strukturen

Ein praktisches Problem beim Einsatz von SPL-Strukturen: Viele Drittanbieter-Bibliotheken und Framework-Komponenten erwarten explizit ein array als Typ-Hint oder Rückgabetyp und akzeptieren SplFixedArray oder SplStack nicht direkt, selbst wenn diese Strukturen dieselbe fachliche Aufgabe erfüllen würden. Eine Konvertierung an der Schnittstelle zu solchen Bibliotheken ist dann unvermeidlich, was den Speichervorteil an genau dieser Stelle wieder zunichtemacht.

Ein zweiter Punkt betrifft SplFixedArray im Speziellen: Da die Größe fest ist, erfordert jedes nachträgliche Wachstum einen expliziten Aufruf von setSize(), der intern eine komplette Neuallokation des zugrundeliegenden Speicherblocks auslöst. Wird SplFixedArray fälschlich für Datenmengen mit unbekannter, häufig wachsender Größe verwendet, entsteht durch wiederholte setSize()-Aufrufe potenziell mehr Overhead, als ein gewöhnliches Array mit seiner eingebauten, automatischen Kapazitätserweiterung verursacht hätte. Die feste Größe ist also eine Stärke nur bei tatsächlich vorab bekannter Elementanzahl.

9. Datenstrukturen im direkten Vergleich

Ein direkter Vergleich zeigt, welche Struktur für welchen Anwendungsfall die richtige Wahl ist.

Struktur Zugriffsmuster Speicherprofil Empfehlung
Gewöhnliches Array Beliebig, String- und Integer-Schlüssel Hoher Overhead pro Element Kleine bis mittlere Datenmengen
SplFixedArray Fortlaufende Integer, feste Größe Niedrig, zusammenhängender Block Große Mengen mit bekannter Größe
SplQueue / SplStack FIFO / LIFO, kein wahlfreier Zugriff Niedrig, verkettete Liste Warteschlangen, konstante Dequeue-Zeit
SplHeap / SplPriorityQueue Sortierter Zugriff auf Extremwert Mittel, Baumstruktur Priorisierte Verarbeitung, kürzeste Wege

Der Vergleich verdeutlicht: Es gibt keine universell beste Struktur, sondern jede SPL-Klasse spezialisiert sich auf ein bestimmtes Zugriffsmuster. Der Speichervorteil entsteht genau dadurch, dass jede Struktur bewusst auf Funktionalität verzichtet, die für das jeweilige Zugriffsmuster nicht gebraucht wird.

Mironsoft

PHP Speicheroptimierung, Batch-Verarbeitung und Datenstruktur-Beratung

Große Datenmengen speichereffizienter verarbeiten?

Wir analysieren Batch-Jobs, Import-Routinen und numerische Verarbeitung auf unnötigen Array-Overhead und migrieren gezielt auf SplFixedArray, SplQueue oder SplHeap, wo sich der Umstieg tatsächlich messbar lohnt.

Speicherprofil-Analyse

Messung des tatsächlichen Overheads bestehender Array-basierter Verarbeitungen

Gezielte SPL-Migration

Umbau von Bulk-Imports und Warteschlangen auf speichereffiziente SPL-Strukturen

Kompatibilitäts-Check

Prüfung, wo Bibliotheks-Schnittstellen eine Konvertierung zurück zu Arrays erzwingen

10. Zusammenfassung

Gewöhnliche PHP-Arrays sind intern geordnete Hashtabellen mit spürbarem Overhead pro Element, unabhängig davon, ob dieser Overhead fachlich gebraucht wird. SplFixedArray, SplStack, SplQueue und SplHeap bieten speichereffiziente Alternativen, die genau auf ihr jeweiliges Zugriffsmuster spezialisiert sind: SplFixedArray für Daten mit fester Größe und sequentiellen Integer-Indizes, SplQueue und SplStack für konstante Zeit bei Einfüge- und Entnahmeoperationen, SplHeap für sortierte Verarbeitung mit logarithmischer Laufzeit.

Der Umstieg lohnt sich vor allem bei großen Datenmengen im sechs- bis siebenstelligen Bereich, bei denen der Speicherverbrauch tatsächlich zum limitierenden Faktor wird. Bei kleineren Datenmengen oder bei häufigem Kontakt mit Bibliotheken, die explizit gewöhnliche Arrays erwarten, überwiegen die Nachteile der eingeschränkten Funktionalität den Speichervorteil meist deutlich.

SplFixedArray & Co, das Wichtigste auf einen Blick

Arrays haben Hashtabellen-Overhead

Jedes Element kostet zusätzlichen Speicher für Hash-Bucket und Verwaltungsdaten, unabhängig vom Werttyp.

SplFixedArray für feste Größe

Zusammenhängender Speicherblock ohne Hash-Overhead, ideal für bekannte Elementanzahl.

SplQueue für konstante Dequeue-Zeit

Vermeidet den linearen Aufwand von array_shift() bei großen Warteschlangen.

Umstieg nur bei tatsächlichem Bedarf

Lohnt sich ab sechs- bis siebenstelligen Elementmengen, sonst überwiegt der Funktionsverlust.

11. FAQ: SplFixedArray & Co statt PHP-Arrays

1Was ist SplFixedArray?
Eine feste, zusammenhängende Speicherstruktur mit Integer-Indizes ab null, ohne Hashtabellen-Overhead.
2Warum verbraucht ein Array mehr Speicher?
Es ist eine Hashtabelle mit zusätzlichem Overhead pro Element, unabhängig vom Werttyp.
3Wie viel spart SplFixedArray?
Häufig dreißig bis fünfzig Prozent bei einfachen Werten, abhängig von PHP-Version und Datentyp.
4Warum ist SplQueue schneller?
dequeue() arbeitet in konstanter Zeit, array_shift() muss alle Elemente verschieben.
5Funktioniert array_map() direkt?
Nein, nur über iterator_to_array(), was den Speichervorteil wieder aufhebt.
6Wofür eignet sich SplHeap?
Für sortierte Verarbeitung mit logarithmischer Laufzeit, etwa priorisierte Warteschlangen.
7Was bei Zugriff außerhalb der Größe?
Eine OutOfRangeException wird ausgelöst, statt stillschweigend ein neues Element anzulegen.
8Ab welcher Größe lohnt sich der Umstieg?
Ab sechs- bis siebenstelligen Elementmengen, sonst ist der Unterschied vernachlässigbar.
9Akzeptieren Bibliotheken SplFixedArray?
Oft nicht direkt, viele erwarten explizit array und erzwingen eine Konvertierung.
10Für wachsende Datenmengen geeignet?
Nein, jedes Wachstum braucht setSize() mit Neuallokation, ein Array ist dafür meist besser.