Вы или троллите, или не можете воспринять два предложения вместе. Оба варианта вас не красят.
Здесь нет вопроса, куда поместить этот код. И поэтому ваш ответ, "генератор это и есть место, чтобы поместить туда этот код" не имеет смысла.
Если читать вопрос целиком, то он звучит очень просто - зачем сначала читать ВЕСЬ миллион записей, и только потом начинать записывать в БД? Почему нельзя это делать поблочно? И это очень хороший, логичный вопрос. На который автор не дал ответа.
Не дали его и вы. Вопрос, повторюсь, был совсем не про взывающий код. А про осмысленность исходного алгоритма - сначала читать, а потом только писать.
Правильным подходом здесь будет прочитать первые десять тысяч и записать их в БД. Потом прочитать следующие 10, и снова записать. И так до конца файла. Это основной, базовый алгоритм. При этом генератор для него совершенно некритичен, он здесь является синтаксическим сахаром. И с ним, и без него, алгоритм поблочной записи работает одинаково. Разница только в "вызывающем коде", который ни к алгоритму, ни вопросу в исходном комментарии отношения не имеет.
Потому что бизнес-требования такие?
И здесь опять вы опять вырвали предложение из контекста. Вопрос формулируется из двух предложений. Второе уточнят первое. То есть вопрос не "зачем извлекать и записывать", а "почему не извлекать порциями?". При том что весь миллион вы всё равно за раз не запихнёте - попросту упрётесь в max_allowed_packet. То есть читать весь миллион - хоть с генератором, хоть без - нет никакого смысла.
Вы здесь из пальца высосали некий метод $db->query(), который исходно возвращает массив. И начали оптимизировать getOrders() через нелепый лимит.
Но оптимизировать здесь надо было сам метод $db->query(). Который сейчас в цикле while получает из бд строки по одной, и зачем-то запихивает их в массив. Вот этот-то цикл и является исходным.
Вместо массива, метод query() должен возвращать переменную, предоставляющую доступ результатам запроса. А дальше у нас есть два варианта:
Использовать этот цикл как есть, как делают миллионы крудошлёпов по всему миру:
$result = $db->query('SELECT * FROM products');
while ($row = $result->fetch()) {
// что-то делаем со строкой
}
Обернуть этот цикл в генератор
function getOrders() {
$result = $db->query('SELECT * FROM products');
while ($row = $result->fetch()) {
yield $row;
}
}
И спокойно использовать его без нелепых ужимок с лимитами, которые заставляют базу каждый раз выполнять один и тот же запрос, а потом ходить по его результату с самого начала всё дальше и дальше, в полном соответствии с алгоритмом маляра Шлемиэля.
И здесь легко увидеть, что исходный цикл стал основой генератора. И поскольку именно этот цикл экономит память, то и генератор, вслед за ним - тоже. И даже вы, если сможете немного умерить свою спесь, тоже это увидите.
В данном случае Вторая древнейшая служба Хабра не при чём. Да, судя по всему, люди и раньше замечали эту несуразность, но хода их открытия не получали. Нужно было чтобы человек сел и сделал полноценное исследование, а не просто "обратил внимание". А такое исследование появилось как раз только две недели назад.
При чём здесь важность? Редактор не проходил по критериям конфликта интересов и ботоводства. Создавать/править статью о себе, спрятавшись за прокси - это кринж, не только в Википедии.
Вы знаете, я и сам, будучи анонимным редактором Википедии, иногда сталкиваюсь с формализмом или автоматизированными откатами, когда проще плюнуть, чем ходить обивать пороги, доказывая адекватность правки. Что не добавляет теплых чувств к администраторам. Есть там и откровенные нарциссы, и самодуры, к сожалению. Тем не менее, система правил там вполне стройная и логичная, и в подавляющем большинстве случаев работает в плюс.
В вашем же случае это уже какая-то патологическая ненависть к ресурсу в целом, вы просто однобоко поливаете его грязью. Увы, к адекватности такой позиции есть большие вопросы.
передачу данных в циклы foreach без предварительной загрузки массива в память
У множества людей какая-то поразительная слепота, они в упор не видят foreach, а видят только "вызывает превышение программой предела памяти". И в итоге пишут полную чушь.
В вашей статье память упоминается десятки раз, но как в мануале - в контексте использования foreach - ни разу. Если бы вы сами следовали информации с "малоизвестного и почти неиспользуемого сайта", каждый раз упоминая foreach в контексте экономии памяти, то претензий бы к вам не было. То есть вместо
значительно сократить использование памяти при обработке больших наборов данных
правильно будет
значительно сократить использование памяти при обработке больших наборов данных через foreach
Ну как разные, если ваш генератор работает только потому, что у него внутри это самое fetch()? 😂 Ну уже смешно, ей-богу :)
Генератор - это обёртка для кода, который экономит память. Конвертирующая этот код в универсальный интерфейс. Но сам по себе он ничего не экономит. Память экономит чтение из базы по одной строке. Генератор же - это такой конвертор, который позволяет результат любого цикла представить в виде массива.
Если исходный цикл экономит память, то и генератор на его основе будет экономить. Если исходный цикл не экономит память, то и генератор на его основе ничего не сэкономит. Ну это уже если совсем на пальцах объяснять эту примитивную логику. Даже так до вас не доходит?
Разработчики говорят, что это способ использовать меньше памяти с циклом foreach. Увы, у большинства читателей этой фразы проблемы с глазами, и часть про foreach они в упор не видят.
Я на этом не останавливался раньше, но стоило, да. Если мне сотрудник принесёт вот такой вот код из вашего комментария,
// Пример: взять первые 100 машин дороже 1 млн, лишние страницы не дергаются
foreach (take(filter(fetchCars(), fn($c) => $c['price'] > 1_000_000), 100) as $car) {
process($car);
}
Код, который каждый раз ходит за выборкой напрямую во внешнее API - а не закэширует сначала данные локально - то такой сотрудник вылетит с работы без выходного пособия.
Согласен, я прочитал невнимательно. Но опять же, вы об этом пишете в комментарии. В статье это ваше "без отказа от массива" исходно не упоминалось вообще, а потом вы добавили короткую отписку "на отвали", всю суть оставив прежней, "ололо, генераторы экономят память!!!".
Потом начитавшись таких статей очередной гений засовывает в генератор массив и гордо пишет "Memory Efficient: Built with generators to process massive XML without running out of memory." И ведь он прав! С точки зрения утверждения "генераторы экономят память" у него всё в порядке. Засунул массив внутрь генератора - сэкономил память.
Именно поэтому ваша статья приносит вред
Причём вред её многократно умножается тем, что она повторяет ту же глупость, что и множество других.
А то что вы начинаете где-то в комментариях добавлять "без отказа от массива" суть статьи не меняет вообще никак.
Теперь давайте разбираться с этим вашим "без отказа от массива". Получается, что генератор экономит память тогда, когда обработчик по какой-то причине требует массив. А требовать он может только ради более оптимального/универсального кода. То есть и получается, что генератор позволяет писать более универсальный код, разделять источник получения данных и их обработчик, сохраняя при этом эффективность по памяти. В от это была бы нормальная формулировка, и вам бы сказали большое спасибо за статью.
Если бы вы сначала показали эффективный по памяти, но организационно неоптимальный код, потом предложили сделать его более универсальным, с разделением источника и обработчика данных с помощью массива - но при этом упёрлись бы в неоптимальность по памяти, и в финале представили бы генератор, завернув в него исходный оптимальный по памяти код, то это была бы прекрасная статья, в которой правильно расставлены акценты: память экономит всё тот же старый while или for, а генератор просто позволяет сделать этот код мобильным, предоставляет универсальный интерфейс для любых циклов.
А так у вас получился просто мусор, непонятно зачем написанный (таких статей и так как грязи) и непонятно для кого.
Да. Но к экономии памяти это отношения не имеет. Есть гении, которые внутри генератора читают файл в массив (ачотакова, генератор сам же всё сэкономит!) и генератор дальше прекрасно отдаёт этот массив, "сохраняя состояние". Но не экономя ни байта памяти.
Генератор не всегда служит для экономии памяти. Когда же речь заходит про "экономию памяти", то это всегда делает цикл, который у генератора него внутри. А удобство генератора в том, что он позволяет выполнять этот цикл не сразу, а в другом месте. А не в "экономии памяти".
Правильно. Генератор это - это удобная обертка. Это про упаковочный материал, а не про память. В эту обёртку, кроме прочего, можно завернуть тривиальный механизм экономии памяти. Но при этом надо понимать, что генератор - это не про сэкономить, а про обернуть то, что экономит.
[генератор нужен] для того, чтобы загружать данные не все сразу
Я попробую последний раз, дальше повторять не вижу смысла. "Чтобы загружать данные не все сразу, а по мере необходимости" служит цикл, который читает из потока по одной ложке. Генератор к этому тривиальному алгоритму никакого отношения не имеет.
Вы сейчас редкую чушь написали. Генератор к этому коду вообще никакого отношения не имеет. Механизмы, которые используются в генераторе - это остановить выполнение, запомнить стек, и вернуть значение.
Некая конкретная реализация генератора может использовать этот код, но и тут не надо переворачивать с ног на голову. Это реализация генератора будет использовать, причём не "те же", а именно этот механизм. Который и без генератора прекрасно справляется. А вот генератор без него не сэкономит и байта. Получается, что память экономит "механизм". А смысл генератора в том, что он может перенести "механизм" в другое место.
Генераторы. Не. Про. Экономию. Памяти. Память они экономить не умеют. Память экономит цикл, который работает у него внутри. Этот цикл без генератора прекрасно сэкономит память. Генератор без этого цикла не с экономит вам ни байта. Реальная польза генераторов не про память. А про то, что вы сами же писали в комментарии выше: про "разделение источника данных и их обработчика" и "унификацию интерфейса".
Это какая-то дурацкая отмазка, я её слышу уже 20 лет. "статья делалась для начинающих" (и это каким-то образом оправдывает наличие в коде SQL инъекций). "Статья писалась для среднего уровня" (и поэтому в ней заявляется рассказ об итераторах, но никакой информации по ним прочему-то не приводится). Впору воскликнуть, вслед за Вовочкой из анекдота, "Где логика, где разум?".
Отсылка к воображаемой аудитории не является универсальным оправданием для кривого кода или дурацкой статьи.
Я вам привел конкретные замечания - упор на экономию памяти, полное отсутствие упоминания настоящих причин использовать генераторы, отсутствие заявленной информации про итераторы. Вы пошли к своему ИИ за исправлениями и ещё больше запутались. В итоге обиделись почему-то на меня и вернули статью в исходное положение. Не думали для разнообразия обидеться на ИИ, который вам это всё подсунул? Или на свою попытку написать статью по теме, в которой сами ничего не понимаете, а полагаетесь на мнение ИИ?
Всё это очень хорошо, но почему-то вы мне об этом рассказываете только сейчас в комментариях. Это до боли напоминает реакцию генеративного ИИ, когда тыкаешь его носом в его bullshit.
В статье я хотел показать
Если честно, то сложно поверить, что вы действительно что-то такое хотели. Если бы хотели, то статья была бы построена совершенно по-другому, с упором на "разделить источник данных и обработку" и "унификацию интерфейса". Но в исходном варианте вы об этом даже не заикнулись - там один только стандартный бубнёж про "экономию памяти". При этом исправленный вариант не стал лучше, акцент так и остался на экономии памяти.
Пример: взять первые 100 машин дороже 1 млн, лишние страницы не дергаются
Это какой-то высосанный из пальца пример. Если в API есть сортировка или условия, то их и надо использовать. Если нет - то надо не с умным лицом делать вот это вот take filter, а тупо загнать всю инфу в локальную БД и уже по ней искать нормальными средствами, а не перебором. И про "лишние страницы не дёргаются" вас обманули. Вы дёргаете лишние страницы до тех пор, пока не наберете случайно разбросанные строки, попадающие под условие. Фасад красивый, а внутри та же неоптимальность. Если задать не миллион, а, скажем, 10, то ваш хвалёный генератор так и будет молотить, пока не переберёт все страницы.
И напоследок - совет: никогда не скармливайте фидбек по статье той ЛЛМ, которая её написала. Она запутается и будет ещё хуже.
В первом сезоне в качестве великого решения всех проблем, позволяющего стать царицею морскою "хранить состояние, управлять ключами или даже динамически менять источник данных", вы нам продавали итератор. А в исправленном и дополненном издании со всеми этими задачами прекрасно справился... генератор! И статья в итоге приобрела лёгкий оттенок шизофрении. Вы уж тогда что ли совсем задвиньте эти итераторы пяткой под шкаф, чтобы не отсвечивали - всё равно они тут были ни к селу, ни к городу.
Неправда. В документации написано "уменьшить расход памяти при использовании foreach". А закончить действительно стоит.
Вы или троллите, или не можете воспринять два предложения вместе. Оба варианта вас не красят.
Здесь нет вопроса, куда поместить этот код. И поэтому ваш ответ, "генератор это и есть место, чтобы поместить туда этот код" не имеет смысла.
Если читать вопрос целиком, то он звучит очень просто - зачем сначала читать ВЕСЬ миллион записей, и только потом начинать записывать в БД? Почему нельзя это делать поблочно? И это очень хороший, логичный вопрос. На который автор не дал ответа.
Не дали его и вы. Вопрос, повторюсь, был совсем не про взывающий код. А про осмысленность исходного алгоритма - сначала читать, а потом только писать.
Правильным подходом здесь будет прочитать первые десять тысяч и записать их в БД. Потом прочитать следующие 10, и снова записать. И так до конца файла. Это основной, базовый алгоритм. При этом генератор для него совершенно некритичен, он здесь является синтаксическим сахаром. И с ним, и без него, алгоритм поблочной записи работает одинаково. Разница только в "вызывающем коде", который ни к алгоритму, ни вопросу в исходном комментарии отношения не имеет.
И здесь опять вы опять вырвали предложение из контекста. Вопрос формулируется из двух предложений. Второе уточнят первое. То есть вопрос не "зачем извлекать и записывать", а "почему не извлекать порциями?". При том что весь миллион вы всё равно за раз не запихнёте - попросту упрётесь в max_allowed_packet. То есть читать весь миллион - хоть с генератором, хоть без - нет никакого смысла.
Вы здесь из пальца высосали некий метод $db->query(), который исходно возвращает массив. И начали оптимизировать getOrders() через нелепый лимит.
Но оптимизировать здесь надо было сам метод $db->query(). Который сейчас в цикле while получает из бд строки по одной, и зачем-то запихивает их в массив. Вот этот-то цикл и является исходным.
Вместо массива, метод query() должен возвращать переменную, предоставляющую доступ результатам запроса. А дальше у нас есть два варианта:
Использовать этот цикл как есть, как делают миллионы крудошлёпов по всему миру:
Обернуть этот цикл в генератор
И спокойно использовать его без нелепых ужимок с лимитами, которые заставляют базу каждый раз выполнять один и тот же запрос, а потом ходить по его результату с самого начала всё дальше и дальше, в полном соответствии с алгоритмом маляра Шлемиэля.
И здесь легко увидеть, что исходный цикл стал основой генератора. И поскольку именно этот цикл экономит память, то и генератор, вслед за ним - тоже. И даже вы, если сможете немного умерить свою спесь, тоже это увидите.
Вспоминается анекдот "Но такая фигня получается!.."
Если это такой динозавр, что о нём по какой-то причине пишут только журналисты (а не палеонтологи), то тут даже и "документалка" не поможет.
Ну вот я такой новичок. Под описанные вами ужасы попадает максимум 5% моих правок. Что я делаю не так?
В данном случае Вторая древнейшая служба Хабра не при чём. Да, судя по всему, люди и раньше замечали эту несуразность, но хода их открытия не получали. Нужно было чтобы человек сел и сделал полноценное исследование, а не просто "обратил внимание". А такое исследование появилось как раз только две недели назад.
При чём здесь важность? Редактор не проходил по критериям конфликта интересов и ботоводства. Создавать/править статью о себе, спрятавшись за прокси - это кринж, не только в Википедии.
Вы знаете, я и сам, будучи анонимным редактором Википедии, иногда сталкиваюсь с формализмом или автоматизированными откатами, когда проще плюнуть, чем ходить обивать пороги, доказывая адекватность правки. Что не добавляет теплых чувств к администраторам. Есть там и откровенные нарциссы, и самодуры, к сожалению. Тем не менее, система правил там вполне стройная и логичная, и в подавляющем большинстве случаев работает в плюс.
В вашем же случае это уже какая-то патологическая ненависть к ресурсу в целом, вы просто однобоко поливаете его грязью. Увы, к адекватности такой позиции есть большие вопросы.
И по поводу мануала. В нём написано.
У множества людей какая-то поразительная слепота, они в упор не видят foreach, а видят только "вызывает превышение программой предела памяти". И в итоге пишут полную чушь.
В вашей статье память упоминается десятки раз, но как в мануале - в контексте использования foreach - ни разу. Если бы вы сами следовали информации с "малоизвестного и почти неиспользуемого сайта", каждый раз упоминая foreach в контексте экономии памяти, то претензий бы к вам не было. То есть вместо
правильно будет
Ну как разные, если ваш генератор работает только потому, что у него внутри это самое fetch()? 😂
Ну уже смешно, ей-богу :)
Генератор - это обёртка для кода, который экономит память. Конвертирующая этот код в универсальный интерфейс. Но сам по себе он ничего не экономит. Память экономит чтение из базы по одной строке. Генератор же - это такой конвертор, который позволяет результат любого цикла представить в виде массива.
Если исходный цикл экономит память, то и генератор на его основе будет экономить. Если исходный цикл не экономит память, то и генератор на его основе ничего не сэкономит. Ну это уже если совсем на пальцах объяснять эту примитивную логику. Даже так до вас не доходит?
Разработчики говорят, что это способ использовать меньше памяти с циклом foreach. Увы, у большинства читателей этой фразы проблемы с глазами, и часть про foreach они в упор не видят.
И отдельно я напишу про
Я на этом не останавливался раньше, но стоило, да. Если мне сотрудник принесёт вот такой вот код из вашего комментария,
Код, который каждый раз ходит за выборкой напрямую во внешнее API - а не закэширует сначала данные локально - то такой сотрудник вылетит с работы без выходного пособия.
Согласен, я прочитал невнимательно. Но опять же, вы об этом пишете в комментарии. В статье это ваше "без отказа от массива" исходно не упоминалось вообще, а потом вы добавили короткую отписку "на отвали", всю суть оставив прежней, "ололо, генераторы экономят память!!!".
Потом начитавшись таких статей очередной гений засовывает в генератор массив и гордо пишет "Memory Efficient: Built with generators to process massive XML without running out of memory." И ведь он прав! С точки зрения утверждения "генераторы экономят память" у него всё в порядке. Засунул массив внутрь генератора - сэкономил память.
Именно поэтому ваша статья приносит вред
Причём вред её многократно умножается тем, что она повторяет ту же глупость, что и множество других.
А то что вы начинаете где-то в комментариях добавлять "без отказа от массива" суть статьи не меняет вообще никак.
Теперь давайте разбираться с этим вашим "без отказа от массива". Получается, что генератор экономит память тогда, когда обработчик по какой-то причине требует массив. А требовать он может только ради более оптимального/универсального кода. То есть и получается, что генератор позволяет писать более универсальный код, разделять источник получения данных и их обработчик, сохраняя при этом эффективность по памяти. В от это была бы нормальная формулировка, и вам бы сказали большое спасибо за статью.
Если бы вы сначала показали эффективный по памяти, но организационно неоптимальный код, потом предложили сделать его более универсальным, с разделением источника и обработчика данных с помощью массива - но при этом упёрлись бы в неоптимальность по памяти, и в финале представили бы генератор, завернув в него исходный оптимальный по памяти код, то это была бы прекрасная статья, в которой правильно расставлены акценты: память экономит всё тот же старый while или for, а генератор просто позволяет сделать этот код мобильным, предоставляет универсальный интерфейс для любых циклов.
А так у вас получился просто мусор, непонятно зачем написанный (таких статей и так как грязи) и непонятно для кого.
Да. Но к экономии памяти это отношения не имеет. Есть гении, которые внутри генератора читают файл в массив (ачотакова, генератор сам же всё сэкономит!) и генератор дальше прекрасно отдаёт этот массив, "сохраняя состояние". Но не экономя ни байта памяти.
Генератор не всегда служит для экономии памяти. Когда же речь заходит про "экономию памяти", то это всегда делает цикл, который у генератора него внутри. А удобство генератора в том, что он позволяет выполнять этот цикл не сразу, а в другом месте. А не в "экономии памяти".
Правильно. Генератор это - это удобная обертка. Это про упаковочный материал, а не про память. В эту обёртку, кроме прочего, можно завернуть тривиальный механизм экономии памяти. Но при этом надо понимать, что генератор - это не про сэкономить, а про обернуть то, что экономит.
Я попробую последний раз, дальше повторять не вижу смысла. "Чтобы загружать данные не все сразу, а по мере необходимости" служит цикл, который читает из потока по одной ложке. Генератор к этому тривиальному алгоритму никакого отношения не имеет.
Вы сейчас редкую чушь написали. Генератор к этому коду вообще никакого отношения не имеет. Механизмы, которые используются в генераторе - это остановить выполнение, запомнить стек, и вернуть значение.
Некая конкретная реализация генератора может использовать этот код, но и тут не надо переворачивать с ног на голову. Это реализация генератора будет использовать, причём не "те же", а именно этот механизм. Который и без генератора прекрасно справляется. А вот генератор без него не сэкономит и байта. Получается, что память экономит "механизм". А смысл генератора в том, что он может перенести "механизм" в другое место.
Ну вам сто раз уже отвечали на этот вопрос.
Генераторы. Не. Про. Экономию. Памяти.
Память они экономить не умеют.
Память экономит цикл, который работает у него внутри.
Этот цикл без генератора прекрасно сэкономит память.
Генератор без этого цикла не с экономит вам ни байта.
Реальная польза генераторов не про память.
А про то, что вы сами же писали в комментарии выше: про "разделение источника данных и их обработчика" и "унификацию интерфейса".
Это какая-то дурацкая отмазка, я её слышу уже 20 лет. "статья делалась для начинающих" (и это каким-то образом оправдывает наличие в коде SQL инъекций). "Статья писалась для среднего уровня" (и поэтому в ней заявляется рассказ об итераторах, но никакой информации по ним прочему-то не приводится). Впору воскликнуть, вслед за Вовочкой из анекдота, "Где логика, где разум?".
Отсылка к воображаемой аудитории не является универсальным оправданием для кривого кода или дурацкой статьи.
Я вам привел конкретные замечания - упор на экономию памяти, полное отсутствие упоминания настоящих причин использовать генераторы, отсутствие заявленной информации про итераторы. Вы пошли к своему ИИ за исправлениями и ещё больше запутались. В итоге обиделись почему-то на меня и вернули статью в исходное положение. Не думали для разнообразия обидеться на ИИ, который вам это всё подсунул? Или на свою попытку написать статью по теме, в которой сами ничего не понимаете, а полагаетесь на мнение ИИ?
Всё это очень хорошо, но почему-то вы мне об этом рассказываете только сейчас в комментариях. Это до боли напоминает реакцию генеративного ИИ, когда тыкаешь его носом в его bullshit.
Если честно, то сложно поверить, что вы действительно что-то такое хотели. Если бы хотели, то статья была бы построена совершенно по-другому, с упором на "разделить источник данных и обработку" и "унификацию интерфейса". Но в исходном варианте вы об этом даже не заикнулись - там один только стандартный бубнёж про "экономию памяти". При этом исправленный вариант не стал лучше, акцент так и остался на экономии памяти.
Это какой-то высосанный из пальца пример. Если в API есть сортировка или условия, то их и надо использовать. Если нет - то надо не с умным лицом делать вот это вот take filter, а тупо загнать всю инфу в локальную БД и уже по ней искать нормальными средствами, а не перебором.
И про "лишние страницы не дёргаются" вас обманули. Вы дёргаете лишние страницы до тех пор, пока не наберете случайно разбросанные строки, попадающие под условие. Фасад красивый, а внутри та же неоптимальность. Если задать не миллион, а, скажем, 10, то ваш хвалёный генератор так и будет молотить, пока не переберёт все страницы.
И напоследок - совет: никогда не скармливайте фидбек по статье той ЛЛМ, которая её написала. Она запутается и будет ещё хуже.
В первом сезоне в качестве великого решения всех проблем, позволяющего
стать царицею морскою"хранить состояние, управлять ключами или даже динамически менять источник данных", вы нам продавали итератор. А в исправленном и дополненном издании со всеми этими задачами прекрасно справился... генератор! И статья в итоге приобрела лёгкий оттенок шизофрении. Вы уж тогда что ли совсем задвиньте эти итераторы пяткой под шкаф, чтобы не отсвечивали - всё равно они тут были ни к селу, ни к городу.