В предыдущей части мы рассмотрели, что такое ZVAL в PHP. В это части более подробно рассмотрим, копирование, ссылки и возможные оптимизации.
Все примеры приведены для php 8.3. Для отладки примеров будем использовать метод xdebug_debug_zval. Для этого должно быть установлено расширение xdebug.
$var = 1;
xdebug_debug_zval('var');
Почему мы используем xdebug_debug_zval() вместо debug_zval_dump()?
Начиная с PHP 8.0, функция debug_zval_dump() перестала отображать критически важную информацию:
Не показывает refcount (счетчик ссылок)
Не отображает is_ref (флаг ссылочности)
В то время как xdebug_debug_zval() продолжает предоставлять полную информацию о внутренней структуре ZVAL:
Примечание: Функция xdebug_debug_zval() всегда показывает значение refcount на 1 больше реального. Это особенность реализации Xdebug — при выводе информации он временно увеличивает счетчик ссылок. В следующих примерах я буду указывать корректные значения refcount (уменьшенные на 1), чтобы отражать реальное состояние переменных.
Это означает, что в памяти хранится только одна копия строки, а все переменные ссылаются на один zval.
7. Измерение потребления памяти
<?php
function test1(): void
{
$data = range(1, 100000);
$memBefore = memory_get_usage();
$copy = $data;
$memAfter = memory_get_usage();
showMemory($memBefore, $memAfter);
}
function test2(): void
{
$data = range(1, 100000);
$memBefore = memory_get_usage();
$copy = $data;
$copy[0] = 1;
$memAfter = memory_get_usage();
showMemory($memBefore, $memAfter);
}
function test3(): void
{
$data = range(1, 100000);
$memBefore = memory_get_usage();
$copy = &$data;
$memAfter = memory_get_usage();
showMemory($memBefore, $memAfter);
}
function test4(): void
{
$data = range(1, 100000);
$memBefore = memory_get_usage();
$copy = &$data;
$copy[0] = 1;
$memAfter = memory_get_usage();
showMemory($memBefore, $memAfter);
}
function showMemory($memBefore, $memAfter): void
{
echo "Память до копирования: {$memBefore} байт\n";
echo "Память после копирования: {$memAfter} байт\n";
echo "Разница: " . ($memAfter - $memBefore) . " байт\n";
}
// 1. Присвоение одного массива другому. Без изменения данных.
test1();
// Память не увеличилась.
// Память до копирования: 2514944 байт
// Память после копирования: 2514944 байт
// Разница: 0 байт
// 2. Присвоение одного массива другому. С изменением данных.
test2();
// В результате память увеличилась в двое, т.к. массив был скопирован.
// Память до копирования: 2514976 байт
// Память после копирования: 4628592 байт
// Разница: 2113616 байт
// 3. Присвовение массива по ссылке, без изменения данных
test3();
// Выделяется память только на ссылку.
// Память до копирования: 2514976 байт
// Память после копирования: 2515008 байт
// Разница: 32 байт
// 4. Присвовение массива по ссылке, с изменением данных
test4();
// Выделяется память только на ссылку.
// Память до копирования: 2514976 байт
// Память после копирования: 2515008 байт
// Разница: 32 байт
В этой статье мы детально разберём структуру zval в современных версиях PHP, начиная с революционного PHP 7. Вы узнаете, как организовано хранение переменных на низком уровне и какие оптимизации были внедрены.
2.1. Основные компоненты zval
Структура _zval_struct
struct _zval_struct {
zend_value value; // Основное значение (64 бита)
union {
struct {
zend_uchar type; // Тип данных (IS_LONG, IS_STRING и др.)
zend_uchar type_flags; // Флаги поведения типа
zend_uchar const_flags; // Флаги константности
zend_uchar reserved; // Зарезервировано
} v;
uint32_t type_info; // Альтернативное представление
} u1; // 32 бита
union u2 { // 32 бита (служебные данные)
uint32_t next; // Для управления хеш-таблицами
uint32_t cache_slot; // Кеширование
uint32_t lineno; // Номер строки (для ошибок)
uint32_t num_args; // Количество аргументов
uint32_t fe_pos; // Позиция в foreach
// ... другие служебные поля
};
}; // Всего 16 байт
1. Компонент value (zend_value)
Тип
Поле
Размер
Описание
Целое
lval
64 бита
Для типов IS_LONG, IS_BOOL
Дробное
dval
64 бита
Для IS_DOUBLE
Указатель
str
64 бита
Для строк (IS_STRING)
Указатель
arr
64 бита
Для массивов (IS_ARRAY)
2. Компонент u1 (метаданные типа)
type — определяет базовый тип данных (IS_LONG, IS_STRING и др.)
type_flags — дополнительные флаги поведения:
IS_TYPE_REFCOUNTED (требует подсчета ссылок)
IS_TYPE_COPYABLE (поддерживает Copy-On-Write)
IS_TYPE_IMMUTABLE (неизменяемый тип)
const_flags — флаги константности
3. Компонент u2 (служебные данные)
Этот компонент используется для различных оптимизаций:
next — связь элементов в хеш-таблицах
cache_slot — кеширование для быстрого доступа
lineno — отладка (номер строки в исходном коде)
num_args — информация о вызовах функций
Практический пример
// Создание zval с целым числом
zval my_zval;
ZVAL_LONG(&my_zval, 42);
// Доступ к данным
if (Z_TYPE(my_zval) == IS_LONG) {
zend_long value = Z_LVAL(my_zval); // 42
php_printf("Значение: %ld\n", value);
}
Zval (Zend value) — фундаментальная структура данных в ядре PHP, ответственная за хранение и обработку всех переменных. В этой статье мы исследуем её эволюцию, современную реализацию и практическое значение для разработчиков.
Историческая эволюция zval
PHP 5: Первое поколение
24-байтовая структура
Универсальный подсчет ссылок
Высокие накладные расходы
PHP 7: Революция
16 байт (экономия 33%)
Разделение value/type
Оптимизация скаляров
PHP 8: Совершенство
Дополнительные оптимизации
Интеграция с JIT
Улучшенные хеш-таблицы
Структура zval в PHP 8
struct _zval_struct {
zend_value value; // 64-битное значение
union {
struct {
zend_uchar type; // Тип данных
zend_uchar type_flags; // Флаги поведения
zend_uchar const_flags; // Константность
zend_uchar reserved; // Выравнивание
} v;
uint32_t type_info;
} u1;
union u2 {
uint32_t next;
// ... служебные поля
};
}; // Всего 16 байт
Ключевые оптимизации
Отказ от refcount для скаляров — целые числа и булевы значения больше не используют подсчет ссылок
Copy-On-Write — сложные структуры копируются только при модификации
Встроенное кеширование хешей — ускорение операций сравнения строк
Оптимизированные хеш-таблицы — быстрый доступ к элементам массивов
Практическое применение для разработчиков и авторов расширений
Оптимизация потребления памяти
Эффективная работа с переменными
Понимание поведения типов
Пример:
// Создание строки в расширении
zend_string *str = zend_string_init("test", 4, 0);
zval zv;
ZVAL_STR(&zv, str);
Zval (Zend value) — фундаментальная структура данных в ядре PHP, ответственная за хранение и обработку всех переменных. В этой статье мы детально разберем её эволюцию и современную реализацию.
Историческая эволюция
Версия PHP
Размер zval
Ключевые изменения
PHP 5.6
24 байта
Универсальный подсчет ссылок, избыточность
PHP 7.0
16 байт
Оптимизация хранения скаляров, новые флаги
PHP 8.2
16 байт
Дополнительные оптимизации JIT
Сравнение производительности
// Тест создания 1 млн zval-ов (в нс)
PHP 5.6: 2400 ns
PHP 8.2: 800 ns (ускорение 3x)
Структура zval в PHP 8
struct _zval_struct {
zend_value value; // 8 байт (union)
union {
struct {
zend_uchar type; // Тип данных
zend_uchar type_flags; // Поведенческие флаги
zend_uchar const_flags; // Константность
zend_uchar reserved; // Выравнивание
} v;
uint32_t type_info; // Альтернативное представление
} u1; // 4 байта
union u2 { // 4 байта (служебные данные)
uint32_t next;
// ... другие поля
};
}; // Всего 16 байт
Типы данных и их хранение
Скаляры (int, float, bool) — хранятся напрямую в zend_value
Строки — указатель на zend_string (отдельная структура в heap)
Массивы — указатель на HashTable
Объекты — указатель на zend_object
Ключевые оптимизации
1. Отказ от refcount для скаляров
В PHP 7+ целые числа и булевы значения больше не используют подсчет ссылок, что устраняет накладные расходы на их копирование.
Строки хранят предвычисленные хеши, что ускоряет операции сравнения и поиска в хеш-таблицах.
Практическое применение
Для разработчиков расширений
// Создание zval в расширении
zval my_zval;
ZVAL_LONG(&my_zval, 42);
// Доступ к значению
if (Z_TYPE(my_zval) == IS_LONG) {
zend_long value = Z_LVAL(my_zval);
}
Оптимизации для веб-приложений
Используйте строгие типы (strict_types=1)
Избегайте избыточного копирования массивов
Освобождайте память unset() для больших структур
Заключение
Zval в PHP 8 — высокооптимизированная структура
Понимание её устройства помогает писать эффективный код
Оптимизации PHP 7+ дают до 3x прирост производительности