Как стать автором
Обновить

Комментарии 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(), то хочется горько плакать

Казалось бы, php 5.4 вышла 11 лет назад...

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории