Как сэкономить память, если нужно обработать большое количество объектов-моделей

от автора

  Пост навеян статьей Сколько памяти потребляют объекты в PHP…, размышлениями над самописной ORM и книгой Мэтта Зандстра «PHP. Объекты, шаблоны и методики программирования» (ISBN 978-5-8459-1689-1).
  Мэтт в главе «Шаблоны баз данных» пишет о том, что если нужно создать несколько тысяч объектов из базы, то для экономии памяти, нужно решать эту задачу не «в лоб», а генерировать объекты по требованию, используя интерфейс Iterator.
  Первая мысль была: «Если мы достали 5000 записей из базы, значит мы хотим все их как-то обработать, и какая разница. сразу будут созданы объекты или по требованию?», но потом понял — если вся работа с каждым объектом происходит внутри цикла foreach или while( next() ), то создание объекта по требованию и автоматическое его уничтожение на следующем витке цикла даст существенную экономию памяти. На деле оказалось — очень существенную.

На чем тестировал: nginx + fast-cgi PHP 5.4.10 + APC.
Код:

// тестовый класс модели Class Model {     public $data = '';     public function __construct($data)     {         $this->data = $data;         }          public function process()     {         $this->data = strtoupper($this->data);     } }  // тестовые данные $data =  str_repeat("jhsdfweurhjk234n", 500);  $start = microtime(1); $collection = new Collection;  // добавляем в коллекцию 5000 записей  for ( $i=0; $i<5000; ++$i ) {     $collection->add($data); }  // что-то делаем со всеми объектами коллекции  foreach( $collection as $item ) {     $item->process(); }    $end = microtime(1); echo "Time: ". round( ($end-$start)*1000 , 2) . " ms <br>"; echo "Memory: ". round(memory_get_usage()/1024 , 2) . " kb <br>"; 

Класс Collection реализован был в двух вариантах:
в первом, объекты создаются сразу при добавлении данных в коллекцию

// коллекция объектов, реализует интерфейс Iterator Class Collection implements Iterator {     protected $position = 0;     protected $items = array();          public function add( $data )     {         // заранее создаем объекты         $this->items[] = new Model($data);      }          function rewind() {         $this->position = 0;     }      function current() {         return $this->items[$this->position];     }      function key() {         return $this->position;     }      function next() {         ++$this->position;     }      function valid() {         return isset($this->items[$this->position]);     } } 

Во втором, объекты создаются только по-требованию, а в коллекции хранятся только данные

Class Collection implements Iterator {     protected $position = 0;     protected $items = array();          public function add( $data )     {         // храним только данные         $this->items[] = $data;      }          function rewind() {         $this->position = 0;     }      function current() {         // создаем объект по требованию         return new Model($this->items[$this->position]);     }      function key() {         return $this->position;     }      function next() {         ++$this->position;     }      function valid() {         return isset($this->items[$this->position]);     } } 

Результаты тестирования:

Способ Время, мс Расход памяти, кб
Создание всех объектов сразу 1389±10 40279,82
Создание объектов по-требованию 1344±10 415,28

Экономия памяти получилась в буквальном смысле в 100 раз.

Популярная ORM Doctrine 1.* уже содержит гидратор Doctrine_Core::HYDRATE_ON_DEMAND, который вроде как реализует ту же логику docs.doctrine-project.org/projects/doctrine1/en/latest/en/manual/data-hydrators.html#on-demand

ссылка на оригинал статьи http://habrahabr.ru/post/164123/


Комментарии

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *