Maison >développement back-end >tutoriel php >Les performances de la mémoire augmentent avec les générateurs et Nikic / iter

Les performances de la mémoire augmentent avec les générateurs et Nikic / iter

Joseph Gordon-Levitt
Joseph Gordon-Levittoriginal
2025-02-16 09:17:10367parcourir

itérateur PHP et générateur: un outil puissant pour un traitement efficace des grands ensembles de données

Les tableaux et les itérations sont la pierre angulaire de toute application. Au fur et à mesure que nous obtenons de nouveaux outils, la façon dont nous utilisons les tableaux devrait également s'améliorer.

Par exemple, un générateur est un nouvel outil. Au début, nous n'avons que des tableaux, puis nous gagnons la possibilité de définir notre propre structure de tableau de classe (appelée itérateurs). Mais depuis PHP 5.5, nous pouvons rapidement créer des structures d'itérateur de classe appelées générateurs.

Memory Performance Boosts with Generators and Nikic/Iter

Le générateur ressemble à des fonctions, mais nous pouvons les utiliser comme itérateurs. Ils nous fournissent une syntaxe simple pour créer des fonctions reproductibles essentiellement interruptibles. Ils sont incroyables!

Nous examinerons plusieurs domaines où les générateurs peuvent être utilisés et explorer certains problèmes qui doivent être prêts attention lors de l'utilisation des générateurs. Enfin, nous apprendrons une grande bibliothèque créée par le talentueux Nikita Popov.

L'exemple de code se trouve sur https://github.com/sitepoint-editors/generators-and-iter.

Points clés

  • Les générateurs (disponibles depuis PHP 5.5) sont des outils puissants pour créer des itérateurs qui permettent la création de fonctions interruptibles et reproductibles, la simplification du traitement de grands ensembles de données et l'amélioration des performances de la mémoire.
  • Nikita Popov crée une bibliothèque Nikic / Iter qui introduit des fonctions qui peuvent être utilisées avec les itérateurs et les générateurs, en économisant une mémoire significative en évitant de créer des tableaux intermédiaires inutiles.
  • Les bibliothèques générateurs et Nikic / Iter sont particulièrement utiles lorsque vous travaillez avec de grands fichiers CSV, qui peuvent gérer de grands ensembles de données sans les charger tous en mémoire à la fois.
  • Bien que les générateurs puissent améliorer considérablement les performances de la mémoire, ils présentent également certains de leurs propres défis, tels que incompatibles avec array_filter et array_map, nécessitant d'autres outils tels que Nikic / iter pour gérer ces données.

Question

Supposons que vous ayez beaucoup de données relationnelles et que vous souhaitez faire des préchargements. Peut-être que les données sont séparées par les virgules, vous devez charger chaque type de données et les regrouper.

Vous pouvez commencer par le code simple suivant:

<code class="language-php">function readCSV($file) {
    $rows = [];

    $handle = fopen($file, "r");

    while (!feof($handle)) {
        $rows[] = fgetcsv($handle);
    }

    fclose($handle);

    return $rows;
}

$authors = array_filter(
    readCSV("authors.csv")
);

$categories = array_filter(
    readCSV("categories.csv")
);

$posts = array_filter(
    readCSV("posts.csv")
);</code>

Vous pouvez ensuite essayer de concaténer les éléments connexes par des fonctions d'itération ou d'ordre supérieur:

<code class="language-php">function filterByColumn($array, $column, $value) {
    return array_filter(
        $array, function($item) use ($column, $value) {
            return $item[$column] == $value;
        }
    );
}

$authors = array_map(function($author) use ($posts) {
    $author["posts"] = filterByColumn(
        $posts, 1, $author[0]
    );

    // 对 $author 进行其他更改

    return $author;
}, $authors);

$categories = array_map(function($category) use ($posts) {
    $category["posts"] = filterByColumn(
        $posts, 2, $category[0]
    );

    // 对 $category 进行其他更改

    return $category;
}, $categories);

$posts = array_map(function($post) use ($authors, $categories) {
    foreach ($authors as $author) {
        if ($author[0] == $post[1]) {
            $post["author"] = $author;
            break;
        }
    }

    foreach ($categories as $category) {
        if ($category[0] == $post[1]) {
            $post["category"] = $category;
            break;
        }
    }

    // 对 $post 进行其他更改

    return $post;
}, $posts);</code>

a l'air bien, non? Alors, que se passe-t-il lorsque nous avons un grand nombre de fichiers CSV à analyser? Analysons un peu l'utilisation de la mémoire ...

<code class="language-php">function formatBytes($bytes, $precision = 2) {
    $kilobyte = 1024;
    $megabyte = 1024 * 1024;

    if ($bytes >= 0 && $bytes < $kilobyte) {
        return $bytes . " b";
    }

    if ($bytes >= $kilobyte && $bytes < $megabyte) {
        return round($bytes / $kilobyte, $precision) . " kb";
    }

    return round($bytes / $megabyte, $precision) . " mb";
}

print "memory:" . formatBytes(memory_get_peak_usage());</code>

(l'exemple de code contient generate.php, que vous pouvez utiliser pour créer ces fichiers CSV ...)

Si vous avez de grands fichiers CSV, ce code doit afficher la quantité de mémoire nécessaire pour relier ces tableaux ensemble. Au moins de la même taille que le fichier que vous devez lire, car PHP doit tout garder en mémoire.

Le générateur vient de secourir!

Une façon d'améliorer ce problème est d'utiliser un générateur. Si vous ne les connaissez pas, c'est le bon moment pour en savoir plus.

Le générateur vous permet de charger une petite quantité de données totales à la fois. Vous n'avez pas à faire grand-chose avec le générateur:

<code class="language-php">function readCSV($file) {
    $rows = [];

    $handle = fopen($file, "r");

    while (!feof($handle)) {
        $rows[] = fgetcsv($handle);
    }

    fclose($handle);

    return $rows;
}

$authors = array_filter(
    readCSV("authors.csv")
);

$categories = array_filter(
    readCSV("categories.csv")
);

$posts = array_filter(
    readCSV("posts.csv")
);</code>

Si vous partez via les données CSV, vous remarquerez que la quantité de mémoire requise sera immédiatement réduite:

<code class="language-php">function filterByColumn($array, $column, $value) {
    return array_filter(
        $array, function($item) use ($column, $value) {
            return $item[$column] == $value;
        }
    );
}

$authors = array_map(function($author) use ($posts) {
    $author["posts"] = filterByColumn(
        $posts, 1, $author[0]
    );

    // 对 $author 进行其他更改

    return $author;
}, $authors);

$categories = array_map(function($category) use ($posts) {
    $category["posts"] = filterByColumn(
        $posts, 2, $category[0]
    );

    // 对 $category 进行其他更改

    return $category;
}, $categories);

$posts = array_map(function($post) use ($authors, $categories) {
    foreach ($authors as $author) {
        if ($author[0] == $post[1]) {
            $post["author"] = $author;
            break;
        }
    }

    foreach ($categories as $category) {
        if ($category[0] == $post[1]) {
            $post["category"] = $category;
            break;
        }
    }

    // 对 $post 进行其他更改

    return $post;
}, $posts);</code>

Si vous avez déjà vu des mégaoctets d'utilisation de la mémoire, vous verrez maintenant des kilo-kilobytes. C'est une énorme amélioration, mais ce n'est pas sans problème.

Tout d'abord, array_filter et array_map ne fonctionnent pas avec les générateurs. Vous devez trouver d'autres outils pour traiter ce type de données. Voici un outil que vous pouvez essayer!

<code class="language-php">function formatBytes($bytes, $precision = 2) {
    $kilobyte = 1024;
    $megabyte = 1024 * 1024;

    if ($bytes >= 0 && $bytes < $kilobyte) {
        return $bytes . " b";
    }

    if ($bytes >= $kilobyte && $bytes < $megabyte) {
        return round($bytes / $kilobyte, $precision) . " kb";
    }

    return round($bytes / $megabyte, $precision) . " mb";
}

print "memory:" . formatBytes(memory_get_peak_usage());</code>

Cette bibliothèque présente certaines fonctions qui peuvent être utilisées avec les itérateurs et les générateurs. Alors, comment obtenez-vous toujours toutes ces données pertinentes sans enregistrer de données en mémoire?

<code class="language-php">function readCSVGenerator($file) {
    $handle = fopen($file, "r");

    while (!feof($handle)) {
        yield fgetcsv($handle);
    }

    fclose($handle);
}</code>

Cela peut être plus simple:

<code class="language-php">foreach (readCSVGenerator("posts.csv") as $post) {
    // 使用 $post 执行某些操作
}

print "memory:" . formatBytes(memory_get_peak_usage());</code>

(La relecte de chaque source de données est inefficace à chaque fois. Envisagez d'enregistrer des données connexes plus petites (telles que les auteurs et les catégories) en mémoire ...)

Autres choses intéressantes

Pour la bibliothèque de Nikic, ce n'est que la pointe de l'iceberg! Vous avez toujours voulu aplatir un tableau (ou itérateur / générateur)?

<code class="language-bash">composer require nikic/iter</code>

Vous pouvez utiliser des fonctions telles que slice et take pour retourner des tranches de variables itérables:

<code class="language-php">// ... (后续代码与原文类似,但使用iter库函数进行优化,此处省略以节省篇幅) ...</code>

Lorsque vous utilisez davantage les générateurs, vous constaterez peut-être que vous n'avez pas toujours à les réutiliser. Considérez l'exemple suivant:

<code class="language-php">// ... (使用iter库函数简化代码,此处省略以节省篇幅) ...</code>

Si vous essayez d'exécuter le code, vous verrez une exception, "Impossible de traverser le générateur fermé". Chaque fonction itérateur de cette bibliothèque a une fonction correspondante swappable:

<code class="language-php">// ... (使用iter\flatten和iter\toArray函数的示例代码,此处省略以节省篇幅) ...</code>

Vous pouvez utiliser cette fonction de mappage plusieurs fois. Vous pouvez même rendre votre propre générateur réwindable:

<code class="language-php">// ... (使用iter\slice和iter\toArray函数的示例代码,此处省略以节省篇幅) ...</code>

Ce que vous en tirez est un générateur réutilisable!

Conclusion

Pour chaque opération de boucle que vous devez considérer, le générateur peut être une option. Ils sont même utiles pour d'autres choses. Lorsque les fonctionnalités linguistiques sont insuffisantes, la bibliothèque de Nikic fournit un grand nombre de fonctions d'ordre supérieur.

utilisez-vous déjà le générateur? Voulez-vous voir plus d'exemples sur la façon de les implémenter dans votre propre application pour certaines améliorations des performances? S'il vous plaît dites-nous!

(La partie FAQ est similaire au texte d'origine et est omise ici pour économiser de l'espace. La partie FAQ peut être éventuellement conservée ou réorganisée au besoin.)

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn