Отсутсвие жесткой типизации в PHP, на первый взгляд упрощает разработку - не нужно задавать типы переменных, заботиться о конвертации - все делает интепритатор. Конечно же за простоту и удобство прийдется платить и чем важнее производительность тем больше. Ниже речь пойдет о любимых массивах, быстрых и простых на первый взгляд, но прожорливых к памяти при больших объемах.
Массивы в PHP - некий универсальных тип данных, для хранения как векторных, так и многомерных списков, устанавливающий соответствие между ключем и значением (причем один массив может содержать и целочисленные, и строчные ключи одновременно). Кругом массивов создана куча функций позволяющих работать с ними как с очередями, стеком, сортировать и др.
Наверняка это удобно работать с универсальным типом данных, для которого, как и везде в PHP, не нужно указывать типизацию - в одну корзину можно ложить и объекты, и строки, да в общем что угодно.
А как же интерпритатор работает с нетипизированными переменными?
Интерпритатор хранит каждую переменную в виде структуры Zval, содержащую поля:
- zvalue_value value - значение переменной
- zend_uint refcount__gc - количество ссылок на переменную (если оно равно 0 - уборщик мусора удаляет переменную и освобождает память)
- zend_uchar type - тип переменной
- zend_uchar is_ref__gc - флаг определяющий, что переменная является ссылкой
Нас интересует поле value, которое содержит значение переменной представленое в виде объединения, содержащего поля для всех типов данных:
typedef union _zvalue_value {
long lval — численные + булевые и ресурсы (их идентификаторы)
double dval — double
struct { char *val; int len; } str - строка
HashTable *ht — массив
zend_object_value obj — объект и null
}
В зависимости от типа присваемых данных, значение записывается в нужное поле (например а=1 запишется в lval).
Давайте взвесим массив
Измерим, сколько памяти занимает один целочисленный элемент массива. Сначала взвесим пустой массив:
$p1 = memory_get_usage();
$a = array();
echo (memory_get_usage() - $p1);
Теперь один элемент:
$p1 = memory_get_usage();
$a = array(1);
echo (memory_get_usage() - $p1);
Ну и чтобы наверняка - два элемента:
$p1 = memory_get_usage();
$a = array(1,2);
echo (memory_get_usage() - $p1);
У меня один элемент занял 136 байт, что вроде бы не так и много. Но представим, что имеем небольшую матрицу, например игровое поле 100х100, каждая клетка которого содержит целочисленную переменную статуса:
136 * 10000 = 1360000 = ~1.4Мб
Ничего так получилось, правда? На одну только небольшую сетку полтора мегабайта. А давайте попробуем вместо переменной статуса, использовать простой объект:
class cellStatus {
protected $status = 0;
public function getStatus() {
return $this->status;
}
public function setStatus($values) {
$this->status = $value;
}
}
У меня получилось 5.6Мб! Представьте что будет, если сделать поле 1000х1000, даже с целочисленными статусами.
Плохи наши дела
Вот и аукнулась нестрогая типизация - переменная, которая при строгой весила бы 8 байт, весит 136, из-за чего сильно увеличивается потребление памяти.
Так что же делать, чтобы избежать огромных затрат? Совет "не использовать массивы" наверно прозвучит глупо и всеравно не поможет, поэтому советую использовать структуры данных SPL. Например, SPL массив из 10 элементов:
$a = new SplFixedArray(10);
использует меньше памяти чем обычный, хотя и накладывает некоторые ограничения.
В тему:
Если пост понравился - нажмите на +1 - мне будет приятно.