В проектировании любой системы ключевую роль играет выбор структуры данных. Сейчас мы с вами попытаемся разобраться с двумя структурами данных, а так же подчеркнем плюсы и минусы каждой. Так же сделаем замеры по памяти.
С чего начнем (Постановка задачи)
Нужно обработать список сущностей. Сама по себе сущность (для простоты) представляет собой модель (объект) в которой всего одно свойство "value".
Model( value: int )
Решение через массив + stdClass
Многие разработчики сразу подумают "если мне нужно обработать список моделей мне нужен массив". Массив самая простая структура данных (На мой взгляд).
Для решения данной проблемы нам подойдет следующий код:
$count = 100;
$list = [];
for ($index = 0; $index <= $count; $index++) {
$model = new stdClass(); // создаем обьект (модель)
$model->value = $index; // указываем значение
$list[$index] = $model; // вносим новую модель в массив
}В чем же плюсы:
Проще (мало кода) - чем проще тем лучше скажут многие.
Минусы:
Ох уж этот stdClass. Мало того что нет интерфейса взаимодействия, так еще возможны утечки памяти. (В этой статье описаны его минусы).
Потребляет памяти больше чем ожидалось. В PHP массивы не самые экономные так еще и stdClass кушает больше чем ожидалось.
Решение через массив + Model class
Текущее решение похоже на (Решение через массив + stdClass). Единственное и важное отличие это создание интерфейса (класса модели)
$count = 100;
$list = [];
class Model {
protected int $value; // значение модели
public function __construct(int $value)
{
$this->value = $value;
}
}
for ($index = 0; $index <= $count; $index++) {
$list[$index] = new Model($index); // вносим новую модель в массив
}Какие плюсы:
Код прост.
Есть интерфейс взаимодействия с моделью.
В виду отсутствия stdClass меньше потребляет памяти.
Какие минусы:
Чуть усложняется работа с массивом (например фильтр возможно потребует нового метода в модель).
В PHP массивы не самые экономные по памяти.
Решение через связанный список
Не многие (в основном профи) могут подумать а может стоит использовать связанный список?
$count = 100;
class LinkedList {
protected int $value; // значение модели
protected ?LinkedList $next = null; // следующий элемент
public function __construct(int $value)
{
$this->value = $value;
}
// добавление следующего элемента
public function add(LinkedList $next): LinkedList
{
$this->next = $next;
return $next;
}
}
$root = new Node(0); // связаный список
for($index = 1; $index <= $count; $index++) {
$node = ($node ?? $root)->add(new Node($index));
}
unset($node);Какие плюсы:
Есть интерфейс взаимодействия
Меньшее потребление памяти
Многие будущие операции будут выполнятся быстрее (смотря как напишет программист)
Какие минусы:
Чуть усложняется работа с данными так как структура другая фильтр будет работать по другому.
Тесты
Наш тест будет происходить на платформе OnlinePHP.io (Можно стразу зайти и проверить результаты)
Вариант решения PHP 8.2 | RAM USAGE | AFTER UNSET |
связанный список | 8'112.00 B | 32.00 B |
массив + Model class | 8'272.00 B | 0.00 B |
массив + stdClass | 44'632.00 B | 416.00 B |
Вариант решения PHP 8.1 | RAM USAGE | AFTER UNSET |
связанный список | 8'112.00 B | 32.00 B |
массив + Model class | 13'904.00 B | 0.0 B |
массив + stdClass | 50'264.00 B | 416.00 B |
Вариант решения PHP 8.0 - 7.4 | RAM USAGE | AFTER UNSET |
связанный список | 8'080.00 B | 0.00 B |
массив + Model class | 13904.00 B | 0.0 B |
массив + stdClass | 50'264.00 B | 416.00 B |
Вывод
Хоть мы тестили на 100 элементах, даже на них видно какой объем памяти занимал тот или иной вариант решения.
Если ваш продукт работает с большим количеством данных, то самый лучший вариант для вас это "Связанный список". Если ваш продукт еще не работает с большим количеством данных, то мой совет не используйте stdClass.
Полный скрипт (для самостоятельных тестов)
Этот код был написан только для ознакомительных целей (Не используйте его в своих проектах).
$count = 100;
class LinkedList {
protected int $value;
protected ?LinkedList $next = null;
public function __construct(int $value) {
$this->value = $value;
}
public function add(LinkedList $next): LinkedList {
$this->next = $next;
return $next;
}
}
class Model {
protected int $value;
public function __construct(int $value) {
$this->value = $value;
}
}
// -----------------------------------
// -----------------------------------
$memory_get_usage = memory_get_usage();
echo 'list before - ' . sprintf( '%.2f B', memory_get_usage() - $memory_get_usage ) . "\n";
$root = new LinkedList(0);
for($i = 1; $i <= $count; $i++) {
$next = ($next ?? $root)->add(new LinkedList($i));
}
unset($next);
echo 'list set ---- ' . sprintf( '%.2f B', memory_get_usage() - $memory_get_usage) . "\n";
unset($root);
echo 'list after -- ' . sprintf( '%.2f B', memory_get_usage() - $memory_get_usage) . "\n\n";
// -----------------------------------
// -----------------------------------
$memory_get_usage = memory_get_usage();
echo 'array + model before - ' . sprintf( '%.2f B', memory_get_usage() - $memory_get_usage) . "\n";
$list = [];
for($index = 0; $index <= $count; $index++) {
$list[$index] = new Model($index);
}
echo 'array + model set ---- ' . sprintf( '%.2f B', memory_get_usage() - $memory_get_usage) . "\n";
$list = [];
unset($list);
echo 'array + model after -- ' . sprintf( '%.2f B', memory_get_usage() - $memory_get_usage) . "\n\n";
// -----------------------------------
// -----------------------------------
$memory_get_usage = memory_get_usage();
echo 'array + stdClass before - ' . sprintf( '%.2f B', memory_get_usage() - $memory_get_usage) . "\n";
$list = [];
for($index = 0; $index <= $count; $index++) {
$model = new stdClass();
$model->value = $index;
$list[$index] = $model;
}
echo 'array + stdClass set ---- ' . sprintf( '%.2f B', memory_get_usage() - $memory_get_usage) . "\n";
$list = [];
unset($list);
echo 'array + stdClass after -- ' . sprintf( '%.2f B', memory_get_usage() - $memory_get_usage) . "\n\n";
