Комментарии 31
Простите, никогда не работал с Битрикс и, возможно, тупой вопрос: а что мешает использовать sql при работе с рекордсетами?
Я к чему?
С точки зрения БД, работа с данными по-строчно, мягко говоря, не очень оптимально. ¯\_(ツ)_/¯
Я так понимаю, что вы имеете в виду построчное обновление. Но в статье речь идет о построчном чтении. Которое как раз является очень оптимальным с точки зрения расходования памяти.
Ну и построчное обновление тоже не так страшно.
А "использовать SQL" при работе с рекордсетами мешает наличие ORM, который предоставляет универсальные методы для работы с данными. И если все приложение построено на использовании этих методов, то "SQL" следует примерять только в крайних случайх, поскольку все эти методы (например извлечение смежных данных и создание экземпляров соответствующих объектов для них) придется писать вручную.
Вы не используете созданный вами генератор, поэтому память была занята только ресурсами, полученными из БД. В первом примере Вы записываете все данные собранных элементов инфоблока в массив.
foreach (getProperties($elements) as $element) {
$items[] = $element;
}
После добавления данного кода во второй пример можно будет сказать, что примеры идентичны. Предполагаю, что "проблему" с потреблением памяти генератор не решит.
Здесь "$propertyes" как раз и является заменой массиву $items. И выполняет функцию генератора. Так что в теории он тут работает. Непонятно только, зачем было переименовывать, это как раз и сбивает с толку.
Хотя для полноты картины стоило бы всё-таки перебрать массив и что-нибудь с ним поделать - там могут быть всякие побочные эффекты. Работал я с одной ORM, которая кэшировала внутри себя все объекты, чтобы потом лишний раз не лезь в базу. Даже если я исправно запрашивал по одному.
Вызов функции getProperties()
не производит итераций, а только создаёт объект класса \Generator
, при этом тело генератора не будет выполнено. Это и есть причина, по которой автор наблюдает "разницу" в памяти. Сам по себе объект \Generator
не является заменой массиву, это разные по концепции вещи.
Если итерировать генератор из статьи до конца, сложив результаты его итераций в массив, получится та же картина с памятью как и в классических циклах.
Является :)
Смысл генератора в том и состоит, чтобы заменить массив с экономией памяти.
А вот складывать "результаты его итераций в массив" будет как раз феерической глупостью.
Я скорее соглашусь с тем, что сам пример в статье высосан из пальца: вместо того чтобы зачем-то сначала складывать в массив, а потом проходить по этому массиву, никто не мешает обрабатывать данные прямо на месте, внутри цикла while. Во всяком случае, в статье такое ограничение не озвучено. А это стоило бы сделать. Поскольку необходимость работы именно с массивом и является единственной причиной использования генератора (в данном контексте).
В мире битрикса типичная ситуация - набить большой и жирный массив, а потом пустить его гулять по компоненту до самого шаблона, пока он не набъёт его нужными данными. Итого, если говорить о памяти, то у нас она занята дважды: данными, занесенными в шаблон, и теми же данными в мега-массиве.
В этом плане инкрементарное внесение данных "в шаблон" решит проблему забития памяти в том плане, что она не будет израсходована дважды на одни и те же данные. Думаю, автору показывать это в рамках данного бенчмарка было бы уже бессмысленно.
Вот другой вопрос, как этот подход натягивать на реалии фреймворка, который многими своими частями до сих пор работает по такой логике, будто у нас на дворе php 5.3, если даже не старше....
Небольшое примечание/замечание: Вообще это довольно популярное заблуждение, что yield
придуман для того, чтобы экономить память. С таким же успехом можно взять обычный Iterator
и в результате можем получить потребление ещё ниже, нежели в случае с генератором + выше производительность.
И хоть использование этого оператора, очевидно, удобнее, нежели писать итератор руками (или лучше просто взять готовые реализации коллекций с поддержкой функции map
), однако он в первую очередь направлен на реализацию кооперативной многозадачности, которая в современно мире уходит в сторону файберов.
Поэтому, считаю, тема сисе генераторов раскрыта не до конца)
Да постоянно надо следить, что используешь по делу, а что нет... Я как-то видел код, где разработчик вдохновился и напихал везде генераторов типа экономить память, а мог бы не экономить, собрать данные в один запрос, распихать индексы в бд и, в общем, быстрее производить вычисления))) Не модно, не молодёжно, без всяких новых фич языка, зато быстрее...Но мы зачем-то экономим память, которой у сервера и так дохрена.
Контекст моей "претензии" был в сторону того, что статья позиционируется как "используем генераторы", но ни слова про то, что основная задача генераторов - это ставить выполнение функции на паузу для переключения контекста исполнения на выполнение других процессов.
А не то, что "инструмент выбран не правильно" ;)
Да, он экономит память, но это лишь побочный эффект того, что в коде не используются массивы в которых хранятся все данные выборки.
P.S. Оригинальная реализация с `while($cursor->getNext()) {}` работает ровно по такому же принципу - не создаёт массив, а каждый раз возвращает новые данные. Вот альтернативный пример как это можно делать, экономя память, вместо генераторов.
Первое, когда разработчики в 23 году используют классы и методы из прошлого, когда уже лет 5 есть орм инфоблоков, это странно.
Второе, если второй пример складывать в массив память загрузится точно также, как выше правильно отметили.
Тут некоретно сформированно сравнение, ввиду малого проффесионализма судя по всему
Зачем? Зачем "второй пример складывать в массив"? Кто-нибудь может объяснить смысл этого глубокомысленного действия? :)
Я правильно понимаю, что все советчики предлагают примерно такой код:
$properties = getProperties($elements); // получили генератор
$items = [];
foreach ($properties as $item) {
$items[] = $item; // "складывать в массив"
}
...
foreach ($items as $item) {
// работаем с каждым элементом
}
вместо того, чтобы просто
$properties=getProperties($elements); // получили генератор
...
foreach ($properties as $item) {
// работаем с каждым элементом
}
"Где логика, где разум"? (с) Вовочка из анекдота.
Когда впервом примере все складывается в массив, а во втором нет, какой смысл измерять память?
Я про то что если данные не нужно обрабатывать дальше как коллекцию, то не нужно в первом примере использовать массив, тогда потребление памяти будет одинаковое.
Повторюсь, само тестирование автором неверно посталено.
Я про то что если данные не нужно обрабатывать дальше как коллекцию
А если нужно?
Вы либо неправильно понимаете о чем я говорю, либо просто любите срач.
Если нам нужно обработать данные в одном месте, их нужно обработать в цикле с гетнекстэлемент, нет смысла в использовании доп прослойке из функции с возвращаемым генератором.
Если нужно допустим ниже в коде еще раз использовать данные, то складывать в массив нужно будет, только в этом случае нужно складывать в массив
только в этом случае нужно складывать в массив
Нет :)
В этом случае ещё можно сложить в генератор. В этом и состоит смысл этой статьи. Вот только подан он на редкость коряво.
Я согласен с вами в том, что тестирование в статье поставлено неверно. Но само по себе использование генератора в данном случае оправдано. И нормальное тестирование - перебором - это бы показало.
Но вот тестировать так, как это предлагаете вы - перемещением данных из генератора в массив - будет ещё большей глупостью.
Каюсь, я сначала не заметил ваше "еще раз". Согласен, при вашем сценарии, для повторного использования, действительно надо добавлять в массив. Но этот сценарий, к сожалению, не имеет отношения к описанному в статье. Что делает вашу критику неуместной.
Если подытожить:
Тут некорректно сформировано сравнение
-- здесь вы правы
если второй пример складывать в массив
А вот это уже глупость. Эта статья именно про то, чтобы не складывать в массив. Вместо которого и используется генератор.
подытожу
- если не нужно складывать в массив, то в первом примере не нужно складывать в массив и замерять память
создавать генератор в данном примере глупость, обработать все можно в цикле while ($element = $elements->GetNextElement())
создавать генератор в данном примере глупость, обработать все можно в цикле
Верно. Это несомненный недостаток статьи, с которым не поспоришь. Если бы вы в своем исходном комментарии озвучили именно его, то вопросов бы не было, я бы горячо с вами согласился. Но вас вместо этого унесло куда-то не туда. Вы зачем-то взяли с потолка это "если второй пример складывать в массив", что попросту не имеет смысла. Второй пример не надо складывать в массив. Его надо использовать вместо массива.
если не нужно складывать в массив
А это уже ваше допущение. Стоит, все-таки, быть терпимее, и толковать неточности в пользу автора. И не додумывать ситуации, в которых код автора действительно не будет иметь смысла. Почему бы не согласиться с тем, что автору нужно складывать в массив? Ситуация ведь совершенно стандартная. Когда у нас не единая простыня, а многослойное приложение, данные не всегда можно обработать на месте, а их нужно передать куда-то ещё. И в этом случае надо использовать массив. Или генератор - для экономии памяти.
в данном случае генератор вы не сможете перепройти как массив, из Ваших сообщений можно подумать, что вы утверждаете, что генератор это массив с экономией памяти)
данные не всегда можно обработать на месте, а их нужно передать куда-то ещё
тогда выборку из базы и ее обработку нужно делать там где и когда она нужна
вы не сможете перепройти
Не смогу. Но повторю ещё раз, это "перепройти" вы выдумали у себя из головы. К данной статье эта ситуация не имеет отношения. Давайте вы это запомните, и не будете больше повторять.
тогда выборку из базы и ее обработку нужно делать там где и когда она нужна
Это весьма спорное утверждение. Если данные необходимо вывести в шаблоне, то, следуя вашей логике, их прямо в в коде шаблона и надо выбирать из БД?
Повторюсь, могут быть разные ситуации. Где-то можно сделать выборку там, где она нужна. А где-то нельзя. В этом случае (и при наличии большого объема данных) вполне оправдано будет использовать генератор.
это уже другой разговор, если мы выводим в шаблоне результат и при условии что в while ($element = $elements->GetNextElement()) происходит еще обработка данных или использование методов класса в данном цикле то да логично использовать генератор... но как Вы любите говорить, это Вы выдумали и не имеет отношения к этой статье
Всем спасибо за уточнения про неточности в коде и нюансы тестирования. Пример в статье действительно несколько надуманный и приводился в исследовательских целях. Но тем не менее, основан на реальных событиях. Был опыт работы с проектом, где предыдущие разрабы настроили выгрузку по крону таким образом, что сервер падал как раз по упомянутым причинам.
Ну а задача статьи была больше в том, чтобы показать неопытным программистам полезность yield для использования в конкретной ситуации, а не рассмотреть тему генераторов целиком и полностью. Опять же исходя из опыта, не все начинающие разработчики битрикс в курсе возможного решения проблемы с памятью на большой выгрузке.
Претензия не в том, что Вы "не рассмотрели тему генераторов целиком и полностью". А в том, что у Вас начальный и конечный код работают по-разному.
Если выкинуть из первого примера одну-единственную строчку:
while ($element = $elements->GetNextElement()) {
$el=$element->GetFields();
$el['props']=$element->GetProperties();
//$items[]=$resElement;
}
то расход памяти внезапно исчезнет. Хотите вариант, похожий на тот, что с генератором? Запросто:
function getProperties($elements) {
if ( $element = $elements->GetNextElement() ) {
$el=$element->GetFields();
$el['props']=$element->GetProperties();
return $el;
}
return false;
}
while ( $el = getProperties($elements) ) {
// тут что-то делаем с элементом
}
Вуаля! Мы получили ту же экономию памяти.
Единственное отличие - необходимость использовать while вместо foreach .
Ну, и чтобы два раза не вставать:
1) у Вас в примере апостроф вместо одинарной кавычки - парсер Хабра это отлично подсветил. То есть код - нерабочий, Вы его даже не запускали.
2) как верно отметили в комментариях, Вы получили генератор, но не использовали его. Тем не менее, время в замерах растёт, как и ожидается. Очевидно, в статье не тот код, который измеряли. Некрасиво так поступать.
3) функция называется getProperties , но возвращает элемент со свойствами. Несколько не соответствует ожиданиям.
На мой взгляд, с таким подходом Вы не популяризуете использование генераторов, а дискредитируете.
Коллега в условиях задачи указал, что надо не просто выгрузить данные, а еще что то сними сделать причем построчно. на верное это не так очевидно из условия. Так вот при условии что надо построчно обработать данные, у нас получается, что либо надо выгружать весь массив в память, либо надо совершать обработку внутри цикла Так вот для того чтобы не обрабатывать данные в цикле и применяется генератор
Проверил на 130 тысячах товара, где просто читаю свойства. Что с генератором, что без 1,5гб сжирает. Так что ни к чему не приводит. Про апостроф уже сказали.
Тут понимаете, какое дело, если бы выкинуть из всей этой схемы Битрикс - тогда тонкие оптимизации имели бы смысл. Но даже если просто подключить самые минимальные битриксовые "заголовки" к простейшему php-скрипту, то потребление памяти подскакивало с нескольких мегабайт сразу на тридцатку, а если уж начать использовать эти битриксовые API, то вообще пиши-пропало. Уверен, с 2013 года, когда я последний раз с этим возился, Битрикс в этом вопросе продвинулся вперед и теперь потребляет памяти ещё больше, чем ранее ? Так что если уж вы правда озабочены вопросом памяти, и ваш сервер правда может упасть от растраты пары сотен лишних мегабайт - ну тогда ваши скрипты должны писаться на базе фреймворка, который об этом будет заботиться.
Когда в 2023 году люди используют array(), то хочется горько плакать
Пример применения генератора в Битрикс: как не ронять сервер на больших выгрузках