Array-Funktionen im Performance-Vergleich: array_map, foreach, Generatoren
AI generated
<?php
8.4
PHP · Array-Funktionen · Generatoren · Performance
Array-Funktionen im Performance-Vergleich
array_map, foreach und Generatoren im direkten Test

Wer bei großen Datenmengen wahllos zwischen array_map, foreach und Generatoren wechselt, riskiert unnötigen Speicherverbrauch und schwer nachvollziehbare Laufzeiten. Dieser Artikel vergleicht die wichtigsten Array-Funktionen in PHP 8.4 mit echten Benchmarks, zeigt, wann Generatoren mit yield den entscheidenden Unterschied machen, und liefert klare Entscheidungsregeln für den Projektalltag.

13 Min. Lesezeit array_map · foreach · Generatoren · yield PHP 8.4

1. Warum die Wahl der richtigen Array-Funktion Performance und Speicher beeinflusst

In PHP gibt es für fast jede Iteration über eine Datenmenge mehrere Wege ans Ziel: die funktionalen Array-Funktionen wie array_map, die klassische foreach-Schleife oder ein Generator mit yield. Alle drei erzeugen am Ende dasselbe fachliche Ergebnis, unterscheiden sich aber fundamental darin, wie viel Speicher sie zu welchem Zeitpunkt belegen und wie schnell das erste Ergebnis verfügbar ist. Wer diese Unterschiede nicht kennt, wählt oft die bequemste statt der passendsten Array-Funktion, was bei kleinen Datenmengen irrelevant ist, bei großen Importen, Exports oder Streaming-Verarbeitungen aber zu spürbaren Problemen führt.

Der zentrale Unterschied liegt darin, ob eine Array-Funktion eager oder lazy arbeitet. array_map und verwandte Funktionen sind eager: Sie verarbeiten die komplette Eingabe sofort und legen das komplette Ergebnis-Array im Speicher ab, bevor die Funktion zurückkehrt. Ein Generator dagegen ist lazy: Er berechnet jedes Element erst dann, wenn es tatsächlich angefordert wird, und hält zu keinem Zeitpunkt die vollständige Ergebnismenge im Speicher. Diese Eigenschaft macht Generatoren zum wichtigsten Werkzeug, wenn Datenmengen die Größe des verfügbaren Arbeitsspeichers erreichen oder übersteigen könnten.

Dieser Artikel geht die drei zentralen Array-Funktionen im Detail durch, zeigt mit konkreten Benchmarks, wie man Laufzeit und Speicherverbrauch selbst misst, und endet mit praktischen Entscheidungsregeln, wann welche Array-Funktion die richtige Wahl ist. Der Fokus liegt dabei bewusst auf Framework-unabhängigem, reinem PHP 8.4, die gezeigten Muster funktionieren unabhängig davon, ob der Code in einem CLI-Skript, einem Batch-Job oder einer Webanwendung läuft.

2. array_map, array_filter, array_reduce: funktionale Array-Funktionen im Detail

Die drei wichtigsten funktionalen Array-Funktionen in PHP sind array_map, array_filter und array_reduce. array_map wendet einen Callback auf jedes Element eines Arrays an und gibt ein neues Array mit den Ergebnissen zurück, wobei die Schlüssel bei einem einzelnen Eingabe-Array erhalten bleiben. array_filter behält nur die Elemente, für die der Callback true zurückgibt, wobei standardmäßig die ursprünglichen Schlüssel erhalten bleiben, was nach dem Filtern häufig ein zusätzliches array_values() zur Neuindizierung erfordert. array_reduce reduziert ein Array schrittweise auf einen einzigen Wert, indem ein Akkumulator durch alle Elemente gereicht wird.

Der entscheidende Vorteil dieser Array-Funktionen liegt in der Lesbarkeit und der Vermeidung von Zustandsvariablen: Statt eine leere Ergebnisvariable zu deklarieren und sie in einer Schleife manuell zu befüllen, beschreibt der Code direkt die Transformation. Der Nachteil ist, dass alle drei Funktionen eager arbeiten. array_map erzeugt ein komplettes neues Array, auch wenn nur ein Bruchteil der Ergebnisse tatsächlich benötigt wird. Bei mehreren verketteten Aufrufen wie array_map(fn, array_filter(array_map(...))) entstehen zudem mehrere vollständige Zwischen-Arrays im Speicher, was bei großen Datenmengen unnötig Arbeitsspeicher bindet.

Ein weiterer Punkt, der bei Array-Funktionen oft übersehen wird: array_map mit mehreren Eingabe-Arrays verhält sich anders als mit einem einzelnen Array. Werden mehrere Arrays übergeben, werden die Schlüssel des Ergebnis-Arrays numerisch neu vergeben, unabhängig von den ursprünglichen Schlüsseln der Eingaben. Dieses Detail führt gelegentlich zu Überraschungen, wenn man erwartet, dass assoziative Schlüssel automatisch erhalten bleiben.


<?php

declare(strict_types=1);

/** @var list<array{id: int, price: float, active: bool}> $products */
$products = [
    ['id' => 1, 'price' => 19.99, 'active' => true],
    ['id' => 2, 'price' => 49.50, 'active' => false],
    ['id' => 3, 'price' => 9.00, 'active' => true],
];

// array_filter: keep only active products, then re-index with array_values
$activeProducts = array_values(array_filter(
    $products,
    static fn (array $product): bool => $product['active'],
));

// array_map: transform each product into a formatted price string
$priceLabels = array_map(
    static fn (array $product): string => sprintf('#%d: %.2f EUR', $product['id'], $product['price']),
    $activeProducts,
);

// array_reduce: fold all prices into a single total
$totalPrice = array_reduce(
    $products,
    static fn (float $carry, array $product): float => $carry + $product['price'],
    0.0,
);

foreach ($priceLabels as $label) {
    echo $label . PHP_EOL;
}

echo sprintf('Total: %.2f EUR', $totalPrice) . PHP_EOL;

3. foreach im Vergleich: Referenzen, Kopien und Speicherverbrauch

foreach ist die direkteste und in vielen Fällen speicherschonendste Art, ein bestehendes Array zu durchlaufen, weil sie kein zusätzliches Ergebnis-Array erzeugt, sofern man keines explizit anlegt. Der Copy-on-Write-Mechanismus von PHP sorgt dafür, dass ein Array beim Übergeben an foreach nicht sofort physisch kopiert wird, sondern nur eine Referenzzählung erhöht wird. Eine echte Kopie entsteht erst, wenn das Array innerhalb der Schleife tatsächlich verändert wird, was foreach im Normalfall zu einer sehr günstigen Operation macht, auch bei großen Arrays.

Ein wichtiger Unterschied bei Array-Funktionen mit foreach ist die Verwendung von Referenzen mit foreach ($array as &$item). Das Kaufmanns-Und erlaubt es, Elemente direkt im Original-Array zu verändern, ohne ein neues Array zurückzugeben, was bei In-Place-Transformationen Speicher spart. Der bekannte Fallstrick dabei: Nach einer Referenz-Schleife bleibt die Variable $item als Referenz auf das letzte Element bestehen. Eine nachfolgende foreach-Schleife mit derselben Variable ohne erneutes & kann dadurch versehentlich das letzte Element überschreiben, ein klassischer und schwer zu findender Bug.

Im direkten Vergleich mit array_map ist foreach bei einfachen Transformationen oft geringfügig schneller, weil kein zusätzlicher Funktionsaufruf-Overhead für jeden Callback-Aufruf entsteht. Der Unterschied wird bei sehr großen Arrays und einfachen Operationen messbar, bei komplexeren Callbacks verschwindet er meist im Rauschen. Der wichtigere Unterschied bleibt aber der Speicherverbrauch bei Verkettung: Eine foreach-Schleife mit direkter Verarbeitung jedes Elements kann Zwischenergebnisse sofort verwerfen, während verkettete array_map-Aufrufe mehrere vollständige Arrays gleichzeitig im Speicher halten.


<?php

declare(strict_types=1);

/** @var list<array{id: int, price: float, active: bool}> $products */
$products = [
    ['id' => 1, 'price' => 19.99, 'active' => true],
    ['id' => 2, 'price' => 49.50, 'active' => false],
    ['id' => 3, 'price' => 9.00, 'active' => true],
];

// Reference-based foreach: modify the array in place, no new array is allocated
foreach ($products as &$product) {
    if ($product['active']) {
        $product['price'] *= 0.9; // apply a 10 percent discount
    }
}
unset($product); // break the reference to avoid the classic last-element bug

// Combined filter + transform in a single pass, no intermediate array
$discountedLabels = [];

foreach ($products as $product) {
    if (!$product['active']) {
        continue;
    }

    $discountedLabels[] = sprintf('#%d: %.2f EUR', $product['id'], $product['price']);
}

foreach ($discountedLabels as $label) {
    echo $label . PHP_EOL;
}

4. Generatoren mit yield: Lazy Evaluation statt vollständiger Arrays

Ein Generator ist eine Funktion, die statt return das Schlüsselwort yield verwendet und dadurch bei jedem Aufruf ein Generator-Objekt zurückgibt, das dem Iterator-Interface entspricht. Der entscheidende Unterschied zu den bisher gezeigten Array-Funktionen: Der Funktionskörper wird nicht sofort vollständig ausgeführt, sondern pausiert nach jedem yield und läuft erst weiter, wenn der nächste Wert tatsächlich angefordert wird, etwa durch eine foreach-Schleife über den Generator. Dieses Verhalten nennt man Lazy Evaluation, und es ist der Grund, warum Generatoren bei großen oder sogar unbegrenzten Datenmengen unverzichtbar sind.

Der Speichervorteil ist fundamental: Während array_map über eine Million Zeilen ein komplettes Array mit einer Million Ergebnissen im Speicher hält, hält ein Generator zu jedem Zeitpunkt nur das aktuell erzeugte Element im Speicher. Das macht Generatoren zur natürlichen Wahl beim Verarbeiten großer CSV-Dateien, beim Streamen von Datenbankergebnissen über PDOStatement::fetch() in einer Schleife, oder beim Erzeugen von potenziell unendlichen Sequenzen, die mit klassischen Array-Funktionen technisch gar nicht abbildbar wären.

Generatoren können auch Schlüssel liefern, indem man yield $key => $value schreibt, und sie können über yield from andere Generatoren oder iterierbare Strukturen delegieren, was das Zusammensetzen mehrerer Generatoren zu einer Pipeline erlaubt, ohne dass an irgendeiner Stelle ein vollständiges Array entsteht. Der Rückgabewert eines Generators, gesetzt über ein reguläres return am Ende der Funktion, ist über Generator::getReturn() erreichbar, allerdings erst, nachdem der Generator vollständig durchlaufen wurde.


<?php

declare(strict_types=1);

/**
 * Lazily read a large CSV file line by line, never holding the full file in memory.
 *
 * @return Generator<int, array{id: int, price: float, active: bool}>
 */
function readProductsFromCsv(string $path): Generator
{
    $handle = fopen($path, 'rb');

    if ($handle === false) {
        throw new RuntimeException(sprintf('Cannot open CSV file "%s"', $path));
    }

    try {
        while (($row = fgetcsv($handle)) !== false) {
            // Yield one parsed row at a time, only ever holding this single row in memory
            yield [
                'id' => (int) $row[0],
                'price' => (float) $row[1],
                'active' => $row[2] === '1',
            ];
        }
    } finally {
        fclose($handle);
    }
}

$totalActivePrice = 0.0;
$activeCount = 0;

// Each row is processed and discarded immediately, memory stays constant
foreach (readProductsFromCsv('products.csv') as $product) {
    if (!$product['active']) {
        continue;
    }

    $totalActivePrice += $product['price'];
    $activeCount++;
}

echo sprintf('Processed %d active products, total: %.2f EUR', $activeCount, $totalActivePrice) . PHP_EOL;

5. Speicherprofil: memory_get_usage() bei großen Datenmengen messen

Behauptungen über den Speicherverbrauch von Array-Funktionen lassen sich in PHP direkt nachmessen, ohne externe Profiling-Tools zu benötigen. Die Funktion memory_get_usage(true) liefert den aktuell vom PHP-Prozess allokierten Speicher inklusive der vom System reservierten, aber noch ungenutzten Blöcke, während memory_get_usage(false) nur den tatsächlich von PHP-Werten belegten Speicher zurückgibt. Für den Vergleich verschiedener Array-Funktionen ist meist memory_get_peak_usage() die aussagekräftigere Metrik, da sie den höchsten jemals erreichten Speicherstand seit Skriptstart zurückgibt, unabhängig davon, ob der Speicher zwischenzeitlich wieder freigegeben wurde.

Beim Messen ist wichtig, den Garbage Collector zu berücksichtigen: Ein Aufruf von gc_collect_cycles() vor der Messung stellt sicher, dass keine zirkulären Referenzen aus vorherigen Testläufen den gemessenen Wert verfälschen. Ebenso sollte man den zu messenden Codeabschnitt in eine eigene Funktion auslegen, damit lokale Variablen nach dem Funktionsende automatisch aus dem Scope fallen und ihr Speicher wieder freigegeben werden kann, bevor die nächste Variante gemessen wird.

Ein typisches Messergebnis bei einem Array mit einer Million Ganzzahlen: array_map mit einer einfachen Transformation belegt für Eingabe- und Ausgabe-Array zusammen leicht mehrere Dutzend Megabyte, während ein äquivalenter Generator mit yield nur wenige Kilobyte konstanten Speicher benötigt, unabhängig von der Gesamtzahl der Elemente. Dieser Unterschied wächst linear mit der Datenmenge und wird bei mehreren Millionen Datensätzen schnell zum limitierenden Faktor, insbesondere wenn memory_limit in der php.ini konservativ konfiguriert ist.


<?php

declare(strict_types=1);

/**
 * Measure peak memory usage of a callable in bytes.
 */
function measurePeakMemory(callable $work): int
{
    gc_collect_cycles();
    $before = memory_get_peak_usage(true);

    $work();

    $after = memory_get_peak_usage(true);

    return $after - $before;
}

function buildWithArrayMap(int $count): array
{
    $numbers = range(1, $count);

    return array_map(static fn (int $n): int => $n * $n, $numbers);
}

function buildWithGenerator(int $count): Generator
{
    for ($i = 1; $i <= $count; $i++) {
        yield $i * $i;
    }
}

$eagerBytes = measurePeakMemory(static function () {
    $result = buildWithArrayMap(1_000_000);
    unset($result);
});

$lazyBytes = measurePeakMemory(static function () {
    $sum = 0;

    foreach (buildWithGenerator(1_000_000) as $square) {
        $sum += $square;
    }
});

echo sprintf('array_map peak delta: %d bytes', $eagerBytes) . PHP_EOL;
echo sprintf('generator peak delta: %d bytes', $lazyBytes) . PHP_EOL;

6. Benchmark-Methodik: hrtime(), Wiederholungen und Fallstricke beim Messen

Für Laufzeitvergleiche zwischen Array-Funktionen ist hrtime(true) die richtige Wahl, da die Funktion einen monotonen Zeitstempel in Nanosekunden liefert, der nicht durch Systemzeit-Anpassungen wie NTP-Synchronisation verfälscht werden kann, im Gegensatz zu microtime(true). Ein einzelner Messlauf ist jedoch fast nie aussagekräftig, da JIT-Warmup, Opcache-Zustand und Betriebssystem-Scheduling erhebliche Schwankungen verursachen können. Der korrekte Ansatz ist, dieselbe Operation mehrfach zu wiederholen, den Median statt des Durchschnitts zu betrachten, und die ersten Durchläufe als Warmup zu verwerfen.

Ein häufiger methodischer Fehler ist es, Array-Funktionen mit winzigen Testdaten von zehn Elementen zu vergleichen. Bei so kleinen Datenmengen dominiert der Overhead des Funktionsaufrufs selbst das Ergebnis, und die gemessenen Unterschiede sagen nichts über das Verhalten bei realistischen Datenmengen von zehntausenden oder Millionen Elementen aus. Ebenso wichtig: Ein Benchmark sollte das Ergebnis der Berechnung tatsächlich verwenden, etwa durch Aufsummieren oder Ausgeben, da der Opcache und gelegentlich auch die Engine selbst reine "totes Code"-Berechnungen ohne sichtbaren Effekt wegoptimieren können.

Bei Vergleichen zwischen eager und lazy arbeitenden Array-Funktionen muss zusätzlich unterschieden werden, ob wirklich alle erzeugten Werte konsumiert werden. Ein Generator, der nur die ersten zehn von einer Million möglichen Werten liefert, weil die konsumierende Schleife vorzeitig mit break abbricht, ist naturgemäß extrem schnell, aber das ist kein fairer Vergleich zu einem array_map-Aufruf, der zwingend alle Werte berechnet. Ein sauberer Benchmark stellt sicher, dass beide Varianten dieselbe tatsächliche Arbeitsmenge verrichten.

Ansatz Speicherverhalten Lesbarkeit Passender Einsatzfall
array_map / array_filter Eager, vollständiges Ergebnis-Array im Speicher Sehr hoch, deklarativ Kleine bis mittlere, endliche Datenmengen
foreach (direkte Iteration) Kein Zusatz-Array, Copy-on-Write nutzbar Mittel, explizit Komplexe Logik, In-Place-Änderungen mit Referenz
Generator mit yield Konstant, unabhängig von der Datenmenge Hoch bei einfacher Logik Große Dateien, DB-Streaming, unbegrenzte Sequenzen
array_reduce Eager, aber kein Ergebnis-Array (nur Akkumulator) Hoch für Aggregationen Summen, Zusammenfassungen aus endlichen Arrays
iterator_to_array(Generator) Materialisiert wieder ein vollständiges Array Mittel Nur wenn eine API zwingend ein Array erwartet

7. Wann sich Generatoren lohnen und wann sie überflüssig sind

Generatoren sind kein Allheilmittel und bei kleinen, bereits vollständig im Speicher vorliegenden Arrays bringen sie keinen Vorteil gegenüber den klassischen Array-Funktionen. Wenn ein Array mit hundert Elementen bereits komplett im Speicher liegt, etwa weil es aus einer Konfigurationsdatei geladen wurde, erzeugt das Umwandeln in einen Generator nur zusätzlichen Overhead durch die Generator-Maschinerie, ohne dass tatsächlich Speicher eingespart wird. Generatoren entfalten ihren Vorteil erst, wenn die Datenquelle selbst lazy ist oder wenn die Gesamtmenge der Daten größer ist, als sinnvoll komplett im Speicher gehalten werden sollte.

Typische Szenarien, in denen sich Generatoren klar lohnen: das zeilenweise Einlesen großer CSV- oder Log-Dateien, das Streamen von Datenbankergebnissen mit tausenden oder Millionen Zeilen ohne vollständigen Result-Set-Puffer, das Erzeugen kombinatorischer Sequenzen wie Permutationen, deren vollständige Materialisierung praktisch unmöglich wäre, sowie Producer-Consumer-Pipelines, bei denen Daten schrittweise verarbeitet und sofort weitergereicht werden sollen, statt auf das vollständige Vorliegen aller Daten zu warten.

Ein Sonderfall, der bei Array-Funktionen häufig übersehen wird: Sobald man auf ein Array wiederholt zugreifen muss, etwa um es zu sortieren, seine Länge über count() zu ermitteln, oder rückwärts zu iterieren, ist ein klassisches Array meist die bessere Wahl. Ein Generator kann grundsätzlich nur einmal vorwärts durchlaufen werden, ein zweiter Iterationsversuch über dasselbe Generator-Objekt löst eine Exception aus. Wer mehrfachen Zugriff auf dieselben Daten benötigt, sollte entweder ein Array verwenden oder bei jedem Bedarf einen neuen Generator erzeugen.

8. Kombinationen: array_map mit Generatoren, iterator_to_array und Pipelines

Die verschiedenen Ansätze schließen sich nicht gegenseitig aus, sondern lassen sich sinnvoll kombinieren. Seit PHP unterstützt array_map auch generatorbasierte, iterierbare Argumente nicht direkt, wohl aber lässt sich ein Generator vorher mit einer eigenen Transformation versehen, indem man den Generator selbst mit yield transformiert, statt ihn zuerst vollständig in ein Array umzuwandeln. Diese Technik wird häufig als "Generator-Pipeline" bezeichnet: Ein Generator liest Rohdaten, ein zweiter Generator transformiert die Werte des ersten, ein dritter filtert sie, und erst am Ende konsumiert eine foreach-Schleife das Endergebnis, ohne dass irgendwo ein komplettes Zwischen-Array entsteht.

Wenn eine bestehende API zwingend ein echtes Array erwartet, etwa weil eine Bibliotheksfunktion array_slice oder sort darauf anwenden muss, lässt sich ein Generator mit iterator_to_array() vollständig materialisieren. Dabei sollte man sich bewusst sein, dass an dieser Stelle der Speichervorteil des Generators komplett verloren geht, das Ergebnis-Array belegt genauso viel Speicher wie ein äquivalenter array_map-Aufruf. iterator_to_array() ist daher kein Ersatz für eine durchdachte Entscheidung, sondern ein Werkzeug für den Grenzfall, in dem eine lazy Datenquelle an eine eager Schnittstelle angebunden werden muss.

Eine weitere nützliche Kombination ist das Zusammenführen mehrerer Generatoren mit yield from, was es erlaubt, mehrere Datenquellen wie mehrere CSV-Dateien nacheinander als einen einzigen, logischen Datenstrom zu behandeln, ohne dass eine Datei komplett verarbeitet sein muss, bevor die nächste beginnt. Diese Technik reduziert nicht nur den Speicherverbrauch, sondern vereinfacht auch den aufrufenden Code erheblich, weil er nicht wissen muss, aus wie vielen Quellen die Daten tatsächlich stammen.


<?php

declare(strict_types=1);

/**
 * @param iterable<int, array{id: int, price: float, active: bool}> $products
 * @return Generator<int, array{id: int, price: float, active: bool}>
 */
function filterActive(iterable $products): Generator
{
    foreach ($products as $product) {
        if ($product['active']) {
            yield $product;
        }
    }
}

/**
 * @param iterable<int, array{id: int, price: float, active: bool}> $products
 * @return Generator<int, string>
 */
function formatAsLabel(iterable $products): Generator
{
    foreach ($products as $product) {
        yield sprintf('#%d: %.2f EUR', $product['id'], $product['price']);
    }
}

/**
 * Merge multiple CSV sources into a single lazy stream.
 *
 * @param list<string> $paths
 * @return Generator<int, array{id: int, price: float, active: bool}>
 */
function mergeCsvSources(array $paths): Generator
{
    foreach ($paths as $path) {
        yield from readProductsFromCsv($path);
    }
}

// A three-stage generator pipeline: no intermediate array is ever fully materialized
$pipeline = formatAsLabel(filterActive(mergeCsvSources(['products_2025.csv', 'products_2026.csv'])));

foreach ($pipeline as $label) {
    echo $label . PHP_EOL;
}

9. Lesbarkeit vs. Performance: praktische Entscheidungsregeln für Array-Funktionen

Die Wahl zwischen den verschiedenen Array-Funktionen sollte keine Bauchentscheidung sein, sondern sich an der tatsächlichen Datenmenge und dem Zugriffsmuster orientieren. Für kleine, endliche Arrays mit wenigen tausend Elementen ist die Lesbarkeit meist wichtiger als die letzte Millisekunde Performance, hier gewinnen array_map, array_filter und array_reduce wegen ihres deklarativen Stils. Vorzeitige Optimierung mit Generatoren an dieser Stelle erhöht nur die Komplexität, ohne einen messbaren Vorteil zu bringen.

Sobald Datenmengen mehrere zehntausend Elemente überschreiten, extern geladen werden, etwa aus einer Datenbank oder Datei, oder theoretisch unbegrenzt sein könnten, sollten Generatoren zur ersten Wahl werden. Das gilt besonders für Import- und Export-Prozesse, Batch-Verarbeitungen und alles, was in einem Cronjob oder einer Warteschlange läuft, wo ein Speicherlimit-Fehler den gesamten Prozess abbrechen lässt. Eine einfache Faustregel: Sobald man überlegt, ob eine Verarbeitung "genug Speicher haben wird", ist das bereits ein starkes Signal, auf einen Generator umzustellen.

foreach bleibt die richtige Wahl, wenn die Logik pro Element zu komplex für einen kompakten Callback wird, wenn mehrere Bedingungen gleichzeitig geprüft werden müssen, oder wenn In-Place-Modifikationen per Referenz benötigt werden. Die drei Ansätze schließen sich dabei nicht gegenseitig aus: Ein typisches, robustes Muster kombiniert einen Generator als speicherschonende Datenquelle mit einer klaren foreach-Schleife für die eigentliche Verarbeitungslogik, statt krampfhaft alles in eine einzige Kette von Array-Funktionen zu pressen.

10. Zusammenfassung

Die Wahl zwischen den verschiedenen Array-Funktionen in PHP 8.4 entscheidet direkt über Speicherverbrauch und Laufzeit, sobald Datenmengen wachsen. array_map, array_filter und array_reduce arbeiten eager und erzeugen vollständige Zwischen- und Ergebnis-Arrays im Speicher, was bei kleinen, endlichen Datenmengen kein Problem darstellt, sich bei großen Mengen aber schnell summiert. foreach bleibt speicherschonend, solange kein zusätzliches Array angelegt wird, und eignet sich besonders für komplexe Logik und In-Place-Änderungen per Referenz.

Generatoren mit yield lösen das Speicherproblem grundlegend, indem sie Werte lazy statt eager berechnen und zu jedem Zeitpunkt nur ein einzelnes Element im Speicher halten, unabhängig von der Gesamtmenge der Daten. Wer diese Entscheidung nicht dem Zufall überlässt, sondern anhand von echten Benchmarks mit hrtime() und memory_get_peak_usage() trifft, ersetzt Vermutungen über Array-Funktionen durch belastbare Zahlen und wählt für jeden Anwendungsfall den Ansatz, der tatsächlich zur Datenmenge passt.

Array-Funktionen im Performance-Vergleich: Das Wichtigste auf einen Blick

Eager vs. lazy

array_map und foreach arbeiten eager mit vollständigen Arrays im Speicher. Generatoren mit yield berechnen lazy und halten konstant wenig Speicher.

Messen statt vermuten

hrtime(true) für Laufzeit, memory_get_peak_usage() für Speicher. Mehrfach messen, Median bilden, Warmup verwerfen.

Generatoren einsetzen bei

Große Dateien, Datenbank-Streaming, unbegrenzte Sequenzen und überall dort, wo Speicherlimits ein Risiko darstellen.

Lesbarkeit bewahren

Bei kleinen, endlichen Arrays gewinnt der deklarative Stil von array_map/array_filter vor vorzeitiger Optimierung.

11. FAQ: Array-Funktionen im Performance-Vergleich

1Ist array_map immer langsamer als foreach?
Nicht grundsätzlich, foreach ist bei einfachen Transformationen oft nur geringfügig schneller. Entscheidender ist der Speicherverbrauch bei verketteten Aufrufen.
2Wann Generator statt array_map?
Bei großen, externen oder potenziell unbegrenzten Datenmengen. Ein Generator hält nur das aktuelle Element im Speicher statt eines vollständigen Ergebnis-Arrays.
3Kann ich einen Generator mehrfach durchlaufen?
Nein, nur einmal vorwärts. Ein zweiter Versuch löst eine Exception aus, bei Mehrfachzugriff Array oder neuen Generator verwenden.
4Wie messe ich Speicherverbrauch korrekt?
memory_get_peak_usage() vor und nach der Ausführung, mit vorherigem gc_collect_cycles() zur Vermeidung verfälschter Werte durch alte Referenzen.
5Warum hrtime() statt microtime()?
hrtime(true) liefert einen monotonen Nanosekunden-Zeitstempel, der nicht durch Systemzeit-Anpassungen wie NTP verfälscht werden kann.
6array_filter mit oder ohne array_values?
array_filter behält ursprüngliche Schlüssel bei, array_values() erzeugt danach ein neu indiziertes Array ab 0.
7Verbraucht yield from zusätzlichen Speicher?
Nein, yield from delegiert die Iteration ohne Zwischenspeicherung in einem Array und erlaubt so lazy Pipelines.
8Wann ist iterator_to_array() sinnvoll?
Nur wenn eine API zwingend ein echtes Array braucht, etwa für sort(). Der Speichervorteil des Generators geht dabei verloren.
9Lohnt sich ein Generator bei hundert Elementen?
In der Regel nicht, der Generator-Overhead überwiegt den kaum vorhandenen Speichervorteil bei so kleinen Arrays.
10Wie vermeide ich unfaire Benchmarks?
Sicherstellen, dass beide Varianten dieselbe Arbeitsmenge verrichten und alle Werte konsumiert werden, kein vorzeitiger break bei nur einer Variante.

Mironsoft

PHP-Performance-Analyse und Speicheroptimierung für datenintensive Prozesse

Läuft euer Import-Skript regelmäßig ins Speicherlimit?

Wir analysieren bestehende Batch- und Import-Prozesse auf ineffiziente Array-Funktionen, führen Generatoren dort ein, wo sie wirklich helfen, und messen den Effekt mit echten Benchmarks statt Vermutungen.

Performance-Audit

Speicherprofil und Laufzeit bestehender Datenverarbeitungen messen

Refactoring auf Generatoren

Speicherlastige array_map-Ketten durch lazy Pipelines ersetzen

Benchmark-Setup

Wiederholbare Performance-Tests für kritische Datenverarbeitungspfade