Метка: оптимизация php

  • Почему после модификации массива refcount становится 0? Разбор работы Copy-on-Write в PHP 8.3

    Разбор работы refcount и Copy-on-Write в PHP

    Рассмотрим неочевидное поведение подсчёта ссылок при работе с массивами в PHP 8.3.
    В предыдущей статье был простой пример

    $original = [1];
    $copy = $original;
    $copy[0] = 999;
    xdebug_debug_zval('copy');
    // Вывод: (refcount=1, is_ref=0)=array (0 => (refcount=0, is_ref=0)=999)
    // Реальный refcount = 0 (новая копия)
    

    Возник вопрос, почему после операции копирования refcount=0. Разберем подробнее в этой статье.

    1. Пошаговый разбор примера

    Шаг 1: Создание массива

    $original = [1];
    xdebug_debug_zval('original');
    // Вывод: (refcount=2, is_ref=0)=array (0 => (refcount=0, is_ref=0)=1)
    

    Объяснение:

    • Реальный refcount=1 (переменная $original)
    • Xdebug добавляет +1 при выводе (поэтому показывает 2)
    • Элемент массива 1 имеет refcount=0 — это оптимизация для чисел

    Шаг 2: Присваивание переменной

    $copy = $original;
    xdebug_debug_zval('original');
    // Вывод: (refcount=3, is_ref=0)=array (...)
    

    Объяснение:

    • Реальный refcount=2 ($original + $copy)
    • Xdebug добавляет +1 (поэтому показывает 3)
    • Обе переменные ссылаются на один zval

    Шаг 3: Модификация массива (COW)

    $copy[0] = 999;
    xdebug_debug_zval('copy');
    // Вывод: (refcount=1, is_ref=0)=array (0 => (refcount=0, is_ref=0)=999)
    

    Ключевой момент:

    • Срабатывает Copy-on-Write — создаётся новая копия массива
    • Xdebug показывает refcount=1, значит реальный refcount=0
    • Это означает, что только $copy ссылается на этот zval
    • Если сделать unset($copy) — массив сразу удалится

    2. Почему реальный refcount=0?

    В PHP 8.3 действуют следующие правила:

    • Новый zval после COW начинается с refcount=0
    • Переменная $copy — единственный владелец этого zval
    • Xdebug добавляет +1 при выводе (поэтому показывает 1)
    • Элементы массива всегда refcount=0 — это оптимизация

    3. Визуализация в памяти

    ОперацияСостояние памяти
    $original = [1][ZVAL1: refcount=1, value=[1]]
    $copy = $original[ZVAL1: refcount=2, value=[1]]
    $copy[0] = 999 [ZVAL1: refcount=1, value=[1]]
    [ZVAL2: refcount=0, value=[999]]

    4. Практические выводы

    • После COW новый массив имеет refcount=0 (Xdebug показывает 1)
    • Это нормальное поведение оптимизированного PHP 8.3
    • Элементы массива всегда refcount=0 — не стоит беспокоиться
    • Для точных измерений используйте memory_get_usage()

    Статья актуальна для PHP 8.3. В более ранних версиях поведение refcount может отличаться.

  • Внутреннее устройство PHP: глубокий разбор структуры zval

    Zval (Zend value) — фундаментальная структура данных в ядре PHP, ответственная за хранение и обработку всех переменных. В этой статье мы детально разберем её эволюцию и современную реализацию.

    Историческая эволюция

    Версия PHPРазмер zvalКлючевые изменения
    PHP 5.624 байтаУниверсальный подсчет ссылок, избыточность
    PHP 7.016 байтОптимизация хранения скаляров, новые флаги
    PHP 8.216 байтДополнительные оптимизации 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+ целые числа и булевы значения больше не используют подсчет ссылок, что устраняет накладные расходы на их копирование.

    2. Copy-On-Write для сложных структур

    $a = [1,2,3]; // Создается HashTable (refcount=1)
    $b = $a;      // Только увеличивается refcount
    $b[] = 4;     // Реальное копирование при изменении

    3. Встроенное кеширование хешей

    Строки хранят предвычисленные хеши, что ускоряет операции сравнения и поиска в хеш-таблицах.

    Практическое применение

    Для разработчиков расширений

    // Создание 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 прирост производительности