“Yield” и деликатная работа с памятью в PHP

NOTES 29.12.21 29.12.21 261
Бесплатные курсына главную сниппетов

Вы когда-нибудь задавались вопросом: “Какая польза от yield в PHP?”. Позвольте мне избавить вас от поиска в Google; Я с удовольствием раскрою вам пару ключевых моментов о yield:

  1. Что такое yield.

  2. Различия между yield и return.

  3. Варианты использования yield.

  4. Заключение.

  5. Ссылки.

1. Что такое “yield”

Функция-генератор выглядит так же, как и обычная функция, за исключением того, что вместо всего лишь одного значения генератор вырабатывает (yields) столько значений, сколько ему нужно.

Взгляните на следующий пример:

function getValues() {
    yield 'value';
    }
    // вывод строки "value"
    echo getValues();

Конечно, это не будет работать. Предыдущий пример выдаст ошибку: Object of class Generator could not be converted to string. Позвольте мне объяснить почему:

2. Различия между “yield” и “return”

Полученная ошибка говорит нам о том, что функция getValues​​() не возвращает строку, как мы могли бы этого ожидать. Давайте проверим ее тип:

function getValues() {
    return 'value';
    }
    var_dump(getValues()); // string(5) "value"
    function getValues() {
    yield 'value';
    }
    var_dump(getValues()); // class Gene(0) {}rator#1 

Класс Generator реализует интерфейс Iterator, поэтому для получения значений вам необходимо проитерировать по результатам функции getValue():

foreach (getValues() as $value) {
    echo $value;
    }
    // можно также сделать это через переменную
    $values = getValues();
    foreach ($values as $value) {
    echo $value;
    }

Но различия на этом не заканчиваются!

Генератор позволяет вам писать код с использованием оператора foreach для итерации по набору данных без необходимости создавать в памяти массив, что, однако, может приводить к превышению лимита памяти.

В следующем примере мы создадим массив из 800000 элементов и вернем его из функции getValues​​(), контролируя память, выделенную для этого фрагмента кода, с помощью функции memory_get_usage(). Мы будем запрашивать потребление памяти через каждые 200000 добавленных элементов, что означает, что контрольных точек будет четыре:

<?php
    function getValues() {
    $valuesArray = [];
    // get the initial memory usage
    echo round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;
    for ($i = 1; $i < 800000; $i++) {
    $valuesArray[] = $i;

    // let us do profiling, so we measure the memory usage
    if (($i % 200000) == 0) {
    // get memory usage in megabytes
    echo round(memory_get_usage() / 1024 / 1024, 2) . ' MB'. PHP_EOL;
    }
    }
    return $valuesArray;
    }

    $myValues = getValues(); // building the array here once we call the function
    foreach ($myValues as $value) {}

Вот такие результаты потребления памяти мы получили для примера, приведенного выше:

0.34 MB
    8.35 MB
    16.35 MB
    32.35 MB

Несколько строк нашего кода потребляют более 30 мегабайт памяти. Каждый раз, когда мы добавляем элемент в массив $valuesArray, мы увеличиваем его размер в памяти.

Давайте рассмотрим тот же пример, только с использованием yield:

<?php
    function getValues() {
    // get the initial memory usage
    echo round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;
    for ($i = 1; $i < 800000; $i++) {
    yield $i;

    // let us do profiling, so we measure the memory usage
    if (($i % 200000) == 0) {
    // get memory usage in megabytes
    echo round(memory_get_usage() / 1024 / 1024, 2) . ' MB'. PHP_EOL;
    }
    }
    }

    $myValues = getValues(); // no action taken until we loop over the values
    foreach ($myValues as $value) {} // start generating values here

Результат для этого варианта кода может вас поразить:

0.34 MB
    0.34 MB
    0.34 MB
    0.34 MB

Конечно это не означает, что вам нужно повсеместно переходить от return к yield, но если в своем приложении вы создаете огромные массивы, которые могут вызывать проблемы с памятью на сервере, yield однозначно будет решением вашей проблемы.

3. Варианты использования “yield”

Существует много вариантов использования yield, но я выделю пару из них:

function getValues() {
    yield 'value';
    return 'returnValue';
    }
    $values = getValues();
    foreach ($values as $value) {}
    echo $values->getReturn(); // 'returnValue'
function getValues() {
    yield 'key' => 'value';
    }
    $values = getValues();
    foreach ($values as $key => $value) {
    echo $key . ' => ' . $value;
    }

Подробнее об этом можно почитать здесь.

4. Заключение

Основная цель этой статьи - показать на примерах, в чем разница между yield и return в контексте потребления памяти. По моему мнению, это очень важно знать каждому разработчику.

5. Ссылки

  1. http://php.net/manual/en/language.generators.syntax.php

  2. http://php.net/manual/en/class.generator.php

  3. http://php.net/manual/en/language.generators.php

  4. http://php.net/manual/en/function.memory-get-usage.php

 

 

на главную сниппетов
Курсы