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
Libérer: 2025-02-16 09:17:10
original
390 Les gens l'ont consulté

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:

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")
);
Copier après la connexion
Copier après la connexion

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

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);
Copier après la connexion
Copier après la connexion

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 ...

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());
Copier après la connexion
Copier après la connexion

(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:

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")
);
Copier après la connexion
Copier après la connexion

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

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);
Copier après la connexion
Copier après la connexion

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!

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());
Copier après la connexion
Copier après la connexion

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?

function readCSVGenerator($file) {
    $handle = fopen($file, "r");

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

    fclose($handle);
}
Copier après la connexion

Cela peut être plus simple:

foreach (readCSVGenerator("posts.csv") as $post) {
    // 使用 $post 执行某些操作
}

print "memory:" . formatBytes(memory_get_peak_usage());
Copier après la connexion

(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)?

composer require nikic/iter
Copier après la connexion

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

// ... (后续代码与原文类似,但使用iter库函数进行优化,此处省略以节省篇幅) ...
Copier après la connexion

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:

// ... (使用iter库函数简化代码,此处省略以节省篇幅) ...
Copier après la connexion

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:

// ... (使用iter\flatten和iter\toArray函数的示例代码,此处省略以节省篇幅) ...
Copier après la connexion

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

// ... (使用iter\slice和iter\toArray函数的示例代码,此处省略以节省篇幅) ...
Copier après la connexion

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 de ce site Web
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
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal