Глубокое понимание нового контейнера zval и механизма подсчета ссылок в PHP7.

задняя часть PHP контейнер Immutable.js

предисловие

Когда я недавно просматривал материалы по сборке мусора PHP7, некоторые примеры кода в Интернете показывали разные результаты при работе в локальной среде, что на некоторое время озадачило меня. Если хорошенько подумать, найти проблему несложно: большинство этих статей относятся к эпохе PHP5.x, а после выпуска PHP7 принята новая структура zval, и релевантной информации относительно мало, поэтому я объединил некоторую информацию, чтобы сделать резюме,Основное внимание уделяется объяснению механизма подсчета ссылок в новом контейнере zval., Если есть какие-либо ошибки, пожалуйста, сообщите.

Новая структура zval в PHP7

Люди Мин не говорят секретных слов, сначала посмотрите на код!

struct _zval_struct {
	union {
		zend_long         lval;             /* long value */
		double            dval;             /* double value */
		zend_refcounted  *counted;
		zend_string      *str;
		zend_array       *arr;
		zend_object      *obj;
		zend_resource    *res;
		zend_reference   *ref;
		zend_ast_ref     *ast;
		zval             *zv;
		void             *ptr;
		zend_class_entry *ce;
		zend_function    *func;
		struct {
			uint32_t w1;
			uint32_t w2;
		} ww;
	} value;
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                zend_uchar    type,         /* active type */
                zend_uchar    type_flags,
                zend_uchar    const_flags,
                zend_uchar    reserved)     /* call info for EX(This) */
        } v;
        uint32_t type_info;
    } u1;
    union {
        uint32_t     var_flags;
        uint32_t     next;                 /* hash collision chain */
        uint32_t     cache_slot;           /* literal cache slot */
        uint32_t     lineno;               /* line number (for ast nodes) */
        uint32_t     num_args;             /* arguments number for EX(This) */
        uint32_t     fe_pos;               /* foreach position */
        uint32_t     fe_iter_idx;          /* foreach iterator index */
    } u2;
};

За подробным описанием структуры вы можете обратиться к статье Брата Берда в конце статьи.Она написана очень подробно.Я не буду играть большими мечами перед Гуан Гонгом.Здесь я лишь предлагаю несколько ключевых моментов:

  1. Переменные в PHP7 делятся наимя переменнойа такжеПеременнаядве части, соответствующиеzval_structи объявлено в немvalue
  2. zval_struct.valueсерединаzend_long,doubleобепростой тип данных, которые могут напрямую хранить определенные значения, в то время как другие сложные типы данных хранят указатель на другие структуры данных.указатель
  3. В PHP7 счетчики ссылок хранятся вvalueвместоzval_struct
  4. NULL,логическийпринадлежитневажнотип данных (где передается логическое значениеIS_FALSEа такжеIS_TRUEдве константы), естественно счетчика ссылок нет
  5. Цитировать(REFERENCE) стал структурой данных, а не просто битом флага, и его структура выглядит следующим образом:
struct _zend_reference {
    zend_refcounted_h gc;
    zval              val;
}
  1. zend_referenceв видеzval_structодин из включенныхvalueтип, тоже есть свойvalзначение, это значение указывает наzval_struct.valueиз. у всех своисчетчик ссылок.

Счетчик ссылок используется для отслеживания того, сколько в настоящее времяzvalуказать на то жеzend_value.

Для шестого пункта см. следующий код:

$a = 'foo';
$b = &$a;
$c = $a;

Структура данных на данный момент выглядит следующим образом:

$a и $b имеют по одномуzval_structконтейнер, иvalueоба указывают на одно и то жеzend_referenceструктура,zend_referenceвстроенныйvalструктура, указывающая на то же самоеzend_string,содержимое строкихранится в нем.

И $c также имеетzval_struct, и его значение может прямо указывать на указанное вышеzend_string, чтобы при копировании не возникало дублирования.

Поговорим о новомzvalВ структуре будут появляться различные явления и причины этих явлений.

вопрос

1. Почему начальное значение счетчика ссылок некоторых переменных равно 0

Феномен

$var_int = 233;
$var_float = 233.3;
$var_str = '233';

xdebug_debug_zval('var_int');
xdebug_debug_zval('var_float');
xdebug_debug_zval('var_str');

/** 输出 **
var_int:
(refcount=0, is_ref=0)int 233

var_float:
(refcount=0, is_ref=0)float 233.3

var_str:
(refcount=0, is_ref=0)string '233' (length=3)
**********/

причина

В PHP7 при присвоении значения переменной операция состоит из двух частей:

  1. Подать заявку на символическое количество (т.е. имя переменной)zval_structструктура
  2. сохранить значение переменной вzval_struct.valueсередина заzvalсуществуетvalueЗначения, которые могут быть сохранены в поле, не будут считаться ссылками на них,Вместо этого назначьте его непосредственно при копировании, типы этой детали:
  • IS_LONG
  • IS_DOUBLE

то есть наш в PHPпластика такжеплавающая точка.

Так почему же счетчик ссылок var_str также равен 0?
Это включает в себя два типа строк в PHP:

  1. interned stringВнутренние строки (имена функций, имена классов, имена переменных, статические строки):
 $str = '233';    // 静态字符串
  1. Обычная строка:
 $str = '233' . time(); 

завнутренняя строкаЧто касается содержимого строки, то оно уникально и неизменно, что эквивалентно строке, определенной в области статических переменных языка C.Их жизненный цикл существует в течение всего периода запроса, и они будут уничтожены и освобождены после выполнения запроса., и естественно нет необходимости в управлении памятью через подсчет ссылок.

2. Почему значение счетчика напрямую изменяется на 2 при присвоении ссылки на целочисленную переменную, переменную с плавающей запятой и статическую строковую переменную?

Феномен

$var_int_1 = 233;
$var_int_2 = &var_int;
xdebug_debug_zval('var_int_1');

/** 输出 **
var_int:
(refcount=2, is_ref=1)int 233
**********/

причина

Помните, что мы сказали в началеzval_structсерединаvalueструктура данных, при присвоении переменнойпластик,плавающая точкаилистатическая строказначение типа, тип данных значенияzend_long,doubleилиzend_string, то значение может быть сохранено непосредственно в value. При копировании по значению создается новыйzval_structЗначения сохраняются в value того же типа данных таким же образом, поэтому значение refcount всегда будет равно 0.

но при использовании&Иная ситуация, когда оператор делает копию ссылки:

  1. PHP это&Переменная, управляемая оператором, применяется дляzend_referenceструктура
  2. будетzend_reference.valueуказать на оригиналzval_struct.value
  3. zval_struct.valueТип данных будет изменен наzend_refrence
  4. будетzval_struct.valueУкажите на только что примененный и инициализированныйzend_reference
  5. подать заявку на новые переменныеzval_structструктуру, будет ли егоvalueуказывает на только что созданныйzend_reference

На данный момент: и $var_int_1, и $var_int_2 имеютzval_structструктуры и ихzval_struct.valueвсе указывают на одно и то жеzend_referenceструктура, поэтому счетчик ссылок для этой структуры равен 2.

Отступление: zend_reference указывает на целое число или значение с плавающей запятой.Если указанный тип значения — zend_string, значение счетчика ссылок на значение равно 1. Счетчик ссылок из xdebug показывает значение счетчика zend_reference (т.е. 2)

3. Почему значение счетчика ссылок исходного массива равно 2

Феномен

$var_empty_arr = [1, 2, '3'];
xdebug_debug_zval('var_empty_arr');

/** 输出 **
var_arr:
(refcount=2, is_ref=0)
array (size=3)
  0 => (refcount=0, is_ref=0)int 1
  1 => (refcount=0, is_ref=0)int 2
  2 => (refcount=1, is_ref=0)string '3' (length=1)
**********/

причина

Это включает в себя другую концепцию в PHP7, называемуюimmutable array(неизменяемый массив).

For arrays the not-refcounted variant is called an "immutable array". If you use opcache, then constant array literals in your code will be converted into immutable arrays. Once again, these live in shared memory and as such must not use refcounting. Immutable arrays have a dummy refcount of 2, as it allows us to optimize certain separation paths.

Неизменяемый массивдаopcacheТип массива, оптимизированный по расширению. Проще говоря, все массивы, результаты которых остаются постоянными после нескольких компиляций, будут оптимизированы какНеизменяемый массив, ниже приведен встречный пример:

$array = [1, 2, time()];

PHP не может знать во время компиляцииtime()возвращаемое значение функции, поэтому $array здесьизменяемый массив.

Неизменяемый массивкак мы упоминали вышевнутренняя строкато же самое, обаНе используйте подсчет ссылок, но разница в том, что значение счетчика внутренней строки всегда равно 0, а неизменяемый массив будет использоватьПсевдосчетное значение2.

Суммировать

  • простой тип данных
    • Целое число (без подсчета ссылок)
    • float (не использует подсчет ссылок)
    • логический (не использует подсчет ссылок)
    • NULL (не использовать подсчет ссылок)
  • сложные типы данных
    • нить
      • обычная строка (использует подсчет ссылок, начальное значение равно 1)
      • Внутренняя строка (счетчик ссылок не используется, значение счетчика ссылок всегда равно 0)
    • множество
      • Обычный массив (с использованием подсчета ссылок, начальное значение равно 1)
      • Неизменяемые массивы (не используйте подсчет ссылок, используйте значение псевдосчетчика, равное 2)
    • Объект (со счетчиком ссылок, начальное значение равно 1)

использованная литература