Search
Write a publication
Pull to refresh
1
1.2
Александр @phpner

User

Send message

Память не расходуется, потому что fetch()в PDO уже работает лениво, возвращает строки по одной и не загружает весь результат в память.

Но вы бесконечно продолжаете приводить примеры с циклами и говорите: "и так работает".

Но смысл не в самом цикле, а в правильном использовании генератора.

Видимо, не до конца понятно, когда генераторы реально нужны, а когда нет.

Попробую объяснить ещё раз.

Заходим на малоизвестный и почти неиспользуемый сайт:
👉 https://www.php.net/manual/en/language.generators.overview.php

И там находим:

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

Дальше читаем :

Простой пример этого — переопределение функции range() как генератора. Стандартная функция range() генерирует массив, который состоит из значений, и возвращает его, что приводит к генерации больших массивов: например, вызов range(0, 1000000) займёт более 100 МБ оперативной памяти.

А теперь посмотрим на ваш код:

while ($row = $stmt->fetch()) {
    // обрабатываем строку
}

Это обычный цикл, который и так работает построчно благодаря fetch() в PDO.
Сравнивать его с генератором и спрашивать «что я делаю не так?» некорректно - это разные механизмы и разные задачи.

Этот цикл без генератора прекрасно сэкономит память.

Ну да, цикл можно заставить работать и без генератора и при этом экономить память, если переписать цикл и работать просто с числами.
Или как вы советуете выше "тупо загнать всё в локальную БД..." 😅

Только вот вопрос был: "Покажи, как тут сэкономить память без отказа от массива".

Читаем внимательно: "без отказа от массива".

Кстати, вы так и не ответили на него, потому что читаете невнимательно.

Ещё один человек, ссылающийся на LLM - это сейчас так модно?

Вопрос был не об "Хотите посчитать сумму чисел от 1 до 10M?". Вопрос был в другом. Вы, кстати, так и не смогли на него ответить, а просто ушли в сторону.

Вместо создания массива генератор экономит память, ведь он не держит все элементы сразу.

Покажи, как тут сэкономить память без отказа от массива?

$arr = range(1, 10_000_000); // это сразу съедает сотни МБ и частo упирается в memory_limit

Никак: сам факт range() уже «прожёг» память.

А вот лениво - без промежуточного массива:

function numbers(int $n): Generator {
    for ($i = 1; $i <= $n; $i++) {
        yield $i;
    }
}

$sum = 0;
foreach (numbers(10_000_000) as $x) {
    $sum += $x;
}

Здесь элементы не хранятся в памяти пачкой - выдаются по одному. Пик памяти остаётся малым и не зависит от "размера массива".

Вы ИИ упомянули уже 3-6 раза.

И не просто упомянули, а прям уже утверждаете 😅

Потеряли веру в людей? 🙂

На самом деле я согласился с вами и добавил то, на что вы указали (это до сих пор есть в статье).

Вы общаетесь не с ИИ, а с реальным человеком, который потратил своё время, знания и силы, чтобы написать эту статью. Я с уважением воспринимаю критику и очень рад довести материал до идеала. Но когда комментарий содержит что-то вроде: "На редкость бессмысленная статья. Выглядит так, как будто мне хотят продать кусок дерева", "Вы пошли к своему ИИ за исправлениями и ещё больше запутались. "…

Честно говоря, вы настроены на негатив в каждом комментарии, и общаться просто пропадает желание.

У вас стандартная позиция - “всё бессмысленно”, и объяснили вы всё не так, как я хотел. Окей, зафиксировал.

Просто обратите внимание, статья изначально писалась для среднего уровня.

Спасибо за подробный разбор 🙌

Вы правы: если задача — один раз пройти CSV и сразу обработать строки, то достаточно while (($row = fgetcsv(...))) {...} и никаких генераторов не нужно. В статье я хотел показать другой ракурс: когда важно разделить источник данных и обработку, чтобы не привязывать логику к месту, где читаем/пагинируем, и чтобы потребитель мог остановиться раньше, отфильтровать и т. п.

Про пример с API: да, можно «прямо в цикле» ходить по страницам. Генератор тут полезен тем, что делает пагинацию ленивой и переиспользуемой — потребитель сможет взять ровно столько, сколько нужно, и теми же конструкциями работать с любым источником (файл, БД, API):

function fetchCars(): Generator {
    for ($page = 1; ; $page++) {
        $data = apiRequest('cars', ['page' => $page]);
        if (empty($data['items'])) break;
        foreach ($data['items'] as $car) yield $car;
    }
}

// Пример: взять первые 100 машин дороже 1 млн, лишние страницы не дергаются
foreach (take(filter(fetchCars(), fn($c) => $c['price'] > 1_000_000), 100) as $car) {
    process($car);
}

function filter(Traversable $src, callable $pred): Generator {
    foreach ($src as $x) if ($pred($x)) yield $x;
}
function take(Traversable $src, int $n): Generator {
    $i = 0; foreach ($src as $x) { if ($i++ >= $n) break; yield $x; }
}

Если цель — именно экономия памяти, она и так достигается стримингом. Фишка генераторов — унификация интерфейса: можно вернуть «поток» из функции без аккумуляции массива или без передачи коллбэка, а дальше свободно комбинировать map/filter/take, менять источник динамически и останавливать обработку раньше (меньше I/O).

Information

Rating
Does not participate
Date of birth
Registered
Activity