Николай Ланец
15 нояб. 2014 г., 20:14

xPDO::getIterator() вместо xPDO::getCollection()

Многие, кто сталкивался с xPDO, знают метод $modx->getCollection(). Он возвращает массив полученных xPDO-объектов. Но есть еще метод $modx->getIterator(), который выполняет примерно тоже самое, только гораздо выгодней в плане экономии ресурсов и быстродействия. Попробую кратко описать разницу. $modx->getCollection() получает и возвращает массив сразу со всеми полученными объектами. То есть у нас в момент выполнения данного метода расходуются ресурсы под наполнение переменной-массива сразу всеми полученными объектами. А $modx->getIterator() просто создает дескриптов для перечисления, не инициализируя объектов напрямую.
Рассмотрим два пример.
Пример 1.
$q = $modx->newQuery('modResource'); $q->limit(15000); $docs = $modx->getCollection('modResource', $q); foreach($docs as $doc){}
Время выполнения: 22 сек. Использование памяти: 389 Mb
Пример 2.
$q = $modx->newQuery('modResource'); $q->limit(15000); $docs = $modx->getIterator('modResource', $q); foreach($docs as $doc){}
Время выполнения: 19 сек. Использование памяти: 8.9 Mb
Как видите, разница во времени не особо большая, но вот в потреблении памяти просто огромная. Почему так получается? В первом случае MODX получает данные всех объектов и набивает их в результирующий массив, а далее уже происходите перечисление его элементов-объектов. А во втором случае объекты инициализируются только на каждом шаге цикла, при этом в единицу времени существует только одна переменная-объект, затираемая на следующем шаге цикла. В итоге память занимает только один объект, а не 15000.
Плюс использования $modx->getIterator() еще и в том, что можно обрубить перечисление, к примеру, еще на первом объекте (в данном случае чисто для примера, по какому-либо условию), что сильно сократит время выполнения скрипта, так как в итоге будет инициализирован только один объект, в то время, как в случае с $modx->getCollection() в любом случае будут инициализированы все 15000 объектов. В связи с этим рассмотри еще два примера.
Пример 1.
$q = $modx->newQuery('modResource'); $q->limit(15000); $docs = $modx->getCollection('modResource', $q); foreach($docs as $doc){ break; }
Время выполнения: 23 сек. Использование памяти: 389 Mb
То есть почти без изменений. Потому что перечисление массива не занимает много времени, а основное время уходит именно на создание пятнадцати тысяч объектов.
Пример 2.
$q = $modx->newQuery('modResource'); $q->limit(15000); $docs = $modx->getIterator('modResource', $q); foreach($docs as $doc){ break; }
Время выполнения: 0.1343 сек. Использование памяти: 8.25 Mb
Вот здесь время выполнения почти мгновенно. Почему? Давайте еще раз распишем логику: 1. MODX формирует и выполняет SQL-запрос (и в первом и во втором случае это одно и то же время). 2. $modx->getIterator() создает дескриптор цикла (но не создает самих xPDO-объектов). 3. Выполняет первый цикл, инициализирует один xPDO-объект и прекращает выполнение цикла операндом break; За счет этого и обеспечивается скорость, так как в первом случае даже если нам надо выполнить всего один шаг цикла, у нас все равно MODX инициирует 15000 объектов.
В общем, берите на вооружение :)
P.S. В новой версии пакета modxSite в getdata-процессоры я наконец-то введу этот метод, что сделает их еще более быстрыми и менее ресурсоемкими.
P.P.S. Кстати, если первый пример заменить на такой:
$q = $modx->newQuery('modResource'); $q->limit(15000); foreach($modx->getCollection('modResource', $q) as $doc){ break; }
то есть не создавать переменную $docs, а сразу в цикл закинуть результат выполнения метода $modx->getCollection(), то получаем более привлекательные цифры: Время выполнения: 22.5 сек. Использование памяти: 26.25 Mb
то есть при использовании итератора на каждом шаге выполнается по запросу? Или при вызове getIterator сразу кладется в буфер вся выборка, а потом из нее формируются объекты?
Нет, не выполняется по запросу. Это равносильно while($row = $s->fetch(xPDO::FETCH_ASSOC)){}. Смотри код.
хорошая заметка))))

Добавить комментарий