В предыдущей части мы рассмотрели, что такое 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 байт