Пост навеян статьей Сколько памяти потребляют объекты в 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
Автор: bardex