В связи с большим объёмом материала, публикацию пришлось разбить на две части. В первой из них я расскажу о том, как менялись реализации
Структура zval в пятой версии выглядит так:
Как видите, конструкция включает в себя
Объединение в языке С – это структура, в которой лишь один компонент может быть активен в данный момент времени, и размер которой равен размеру самого большого компонента. Все компоненты объединения хранятся в памяти в одном месте и могут интерпретироваться по-разному, в зависимости от того, к кому из них вы обращаетесь. Если считать
Чтобы узнать, какой компонент объединения используется в данный момент, можно посмотреть текущее значение свойства type:
За некоторыми исключениями, zval в PHP 5 располагаются в куче. Поэтому PHP нужно как-то отслеживать: какие именно
Обратите внимание, что ссылки, которые подсчитывает refcount (сколько раз на данный момент используется какое-то значение), не имеют ничего общего с PHP-ссылками (использующими &). Чтобы не возникало путаницы, далее по тексту будем использовать термины «ссылки» и «PHP-ссылки». Последние мы пока что не рассматриваем.
Схожая с подсчётом ссылок идея лежит в основе «копирования при записи». Совместно использовать
В этом примере показано копирование при записи и уничтожение
У подсчёта ссылок есть один серьёзный недостаток: этот механизм не способен определять циклические ссылки. Для этого в PHP используется дополнительный инструмент — циклический сборщик мусора. Каждый раз, когда значение refcount уменьшается и возникает вероятность, что
Для обеспечения работы этого циклического сборщика используется следующая структура:
Структура zval_gc_info включает в себя обычный
Поговорим немного о размерах (всё нижесказанное относится к 64-битным системам). Объединение
И здесь закрадываются сомнения в эффективности реализации
Уместно задать вопрос: действительно ли хранение простых целочисленных требует подсчёта ссылок, использования циклического сборщика и размещения в куче? Конечно, нет. Вот список основных проблем, связанных с реализацией
В седьмой версии языка мы получили новую реализацию
Вот как выглядит структура нового
Первый компонент остался практически таким же, это объединение
Также здесь есть одна небольшая проблема.
В PHP 7 объединение
Обратите внимание, что
Конечно, в данной структуре имеется и refcount. Кроме того, здесь присутствуют
Я уже упоминал, что
Когда-то нужно было копировать
Откуда PHP знает, что используется подсчёт? Это нельзя определить по одному лишь типу, поскольку некоторые типы не используют refcount — например, строки и массивы. Для этого используется один бит компонента
Несколько бит также используется для кодирования свойств типа:
Три основных свойства, которые может иметь тип:
Ниже приведена таблица, в которой показано, какие флаги могут использовать те или иные типы. Под «простыми» подразумеваются типы вроде целочисленных или булевых, которые не используют указатель на внешнюю структуру. Во второй части я также подробнее разберу неизменяемые массивы.
Давайте рассмотрим два примера того, как на практике работает управление
Поскольку целочисленные значения больше не расшариваются, то обе переменные используют разные
Теперь второй пример, здесь уже используется сложное значение:
Каждая переменная всё ещё имеет отдельный (встроенный)
Какие типы поддерживаются в PHP 7:
В чём отличия от PHP 5:
Тип
В следующей части мы подробнее рассмотрим реализацию индивидуальных типов
В PHP 7 кардинально изменился подход к использованию & PHP-ссылок. И это стало одной из основных причин появления багов. Для начала вспомним, как это реализовано в PHP 5. В обычной ситуации принцип «копирование при записи» подразумевает, что
Для PHP-ссылок это не годится. Если значение является PHP-ссылкой, то вы сами захотите поменять его для каждого его пользователя. В PHP 5 флаг
С подобным подходом связана одна значительная проблема: невозможно расшаривать значение между двумя переменными, одна из которых является PHP-ссылкой, а другая нет.
Такое поведение приводит к тому, что при использовании ссылок производительность оказывается ниже, чем при использовании нормальных значений. Вот менее затейливый пример, иллюстрирующий данную проблему:
Теперь посмотрим, как PHP-ссылки реализованы в седьмой версии. Поскольку
По сути,
На вышеприведённых примерах рассмотрим семантику PHP 7. Для краткости возьмём только структуру, на которую ссылаются индивидуальные
Новый
Важное различие между седьмой и пятой версиями заключается в том, что все переменные теперь могут использовать один и тот же массив, вне зависимости от того, являются ли они PHP-ссылками или нет. Массив будет отделён только после внесения некоторых изменений. То есть в PHP 7 можно безопасно передать в
Подведём итоги: главное нововведение в PHP 7 заключается в том, что
zval
(Zend value) начиная с пятой версии PHP. Также обсудим реализацию ссылок. Во второй части будет подробно рассмотрена реализация отдельных типов данных, таких как строки и объекты.zval’ы в PHP 5
Структура zval в пятой версии выглядит так:
typedef struct _zval_struct {
zvalue_value value;
zend_uint refcount__gc;
zend_uchar type;
zend_uchar is_ref__gc;
} zval;
Как видите, конструкция включает в себя
value
, type
и дополнительную информацию __gc
, о чём я расскажу ниже. Value
представляет собой объединение различных возможных значений, которые может хранить zval:typedef union _zvalue_value {
long lval; // Для булевых, целочисленных и ресурсов
double dval; // Для чисел с плавающей запятой
struct { // Для строковых
char *val;
int len;
} str;
HashTable *ht; // Для массивов
zend_object_value obj; // Для объектов
zend_ast *ast; // Для констант
} zvalue_value;
Объединение в языке С – это структура, в которой лишь один компонент может быть активен в данный момент времени, и размер которой равен размеру самого большого компонента. Все компоненты объединения хранятся в памяти в одном месте и могут интерпретироваться по-разному, в зависимости от того, к кому из них вы обращаетесь. Если считать
lval
, то его значение будет интерпретировано как знаковое целочисленное. Значение dval
будет представлено в виде числа двойной точности с плавающей запятой. И так далее.Чтобы узнать, какой компонент объединения используется в данный момент, можно посмотреть текущее значение свойства type:
#define IS_NULL 0 /* Значение не используется */
#define IS_LONG 1 /* Используется lval */
#define IS_DOUBLE 2 /* Используется dval */
#define IS_BOOL 3 /* Используется lval со значениями 0 и 1 */
#define IS_ARRAY 4 /* Используется ht */
#define IS_OBJECT 5 /* Используется obj */
#define IS_STRING 6 /* Используется str */
#define IS_RESOURCE 7 /* Используется lval в качестве resource ID */
/* Специальные типы, используемые для позднего связывания констант */
#define IS_CONSTANT 8
#define IS_CONSTANT_AST 9
Подсчёт ссылок в PHP 5
За некоторыми исключениями, zval в PHP 5 располагаются в куче. Поэтому PHP нужно как-то отслеживать: какие именно
zval
сейчас используются и какие нужно очистить. Для этого используется подсчёт ссылок. Компонент refcount__gc
как раз и хранит информацию о том, сколько раз ссылались на zval
. Например, в $a = $b = 42
на значение 42 ссылаются две переменные, поэтому refcount равен 2. Если значение refcount равно нулю, это означает, что значение не используется и может быть очищено.Обратите внимание, что ссылки, которые подсчитывает refcount (сколько раз на данный момент используется какое-то значение), не имеют ничего общего с PHP-ссылками (использующими &). Чтобы не возникало путаницы, далее по тексту будем использовать термины «ссылки» и «PHP-ссылки». Последние мы пока что не рассматриваем.
Схожая с подсчётом ссылок идея лежит в основе «копирования при записи». Совместно использовать
zval
можно лишь до тех пор, пока он не изменяется. Для видоизменения расшаренного zval
его нужно дублировать (отделить) и все операции проводить уже с копией.В этом примере показано копирование при записи и уничтожение
zval’а
:$a = 42; // $a -> zval_1(type=IS_LONG, value=42, refcount=1)
$b = $a; // $a, $b -> zval_1(type=IS_LONG, value=42, refcount=2)
$c = $b; // $a, $b, $c -> zval_1(type=IS_LONG, value=42, refcount=3)
// Следующая строка дублирует zval
$a += 1; // $b, $c -> zval_1(type=IS_LONG, value=42, refcount=2)
// $a -> zval_2(type=IS_LONG, value=43, refcount=1)
unset($b); // $c -> zval_1(type=IS_LONG, value=42, refcount=1)
// $a -> zval_2(type=IS_LONG, value=43, refcount=1)
unset($c); // zval_1 уничтожен, потому что refcount=0
// $a -> zval_2(type=IS_LONG, value=43, refcount=1)
У подсчёта ссылок есть один серьёзный недостаток: этот механизм не способен определять циклические ссылки. Для этого в PHP используется дополнительный инструмент — циклический сборщик мусора. Каждый раз, когда значение refcount уменьшается и возникает вероятность, что
zval
стал частью цикла, он записывается в root buffer. Когда этот буфер заполняется, потенциальные циклы помечаются и зачищаются сборщиком мусора. Для обеспечения работы этого циклического сборщика используется следующая структура:
typedef struct _zval_gc_info {
zval z;
union {
gc_root_buffer *buffered;
struct _zval_gc_info *next;
} u;
} zval_gc_info;
Структура zval_gc_info включает в себя обычный
zval
и дополнительный указатель. Указатель u
, являющийся объединением, используется для обозначения одного из двух типов. Указатель buffered
хранит информацию о том, откуда в root buffer
ссылаются на zval
. В случае уничтожения zval
указатель уничтожается до момента запуска циклического сборщика (что весьма удобно), next
используется при уничтожении значений сборщиком.Необходимость перемен
Поговорим немного о размерах (всё нижесказанное относится к 64-битным системам). Объединение
zvalue_value
занимает 16 байт, поскольку тот же размер имеют str
и obj
. Вся структура zval
занимает 24 байта, а zval_gc_info
— 32 байта. Помимо прочего, размещение zval
в куче дополнительно потребляет 16 байт. Итого на каждый zval
приходится по 48 байт, вне зависимости от количества мест, где он используется.И здесь закрадываются сомнения в эффективности реализации
zval
. Судите сами: допустим, он хранит простое целочисленное, которое само по себе занимает 8 байт. Также в любом случае нужно хранить и метку типа, которая занимает один байт, но из-за структуры требует все восемь. К получившимся 16 байтам надо добавить ещё 16 для нужд подсчёта ссылок и циклического сборщика мусора, и ещё 16 — для размещения в куче. Не говоря о том, что сами операции размещения и последующего удаления потребляют немало ресурсов.Уместно задать вопрос: действительно ли хранение простых целочисленных требует подсчёта ссылок, использования циклического сборщика и размещения в куче? Конечно, нет. Вот список основных проблем, связанных с реализацией
zval
в PHP 5:Zval
(почти) всегда требуется размещать в куче.Zval
всегда требуют использования подсчёта ссылок и сбора информации о циклах. Даже в тех случаях, когда расшаривание значений не стоит потраченных ресурсов (целочисленные) или циклы не могут возникнуть в принципе.- Прямой подсчёт ссылок приводит к двойному выполнению этой процедуры в случае с объектами и ресурсами. Причину этого явления я разберу во второй части публикации.
- В некоторых случаях приходится прибегать к большому количеству обходных манёвров. Например, чтобы получить доступ к объекту, хранящемуся в переменной, необходимо суммарно разыменовать четыре указателя, со всеми сопутствующими цепочками. Об этом я тоже поговорю во второй части.
- Прямой подсчёт ссылок также означает, что значения можно расшаривать только между
zval
’ами. Например, строку невозможно совместно использовать вzval
и ключе хэш-таблицы (без хранения этого ключа также в виде zval).
Zval’ы в PHP 7
В седьмой версии языка мы получили новую реализацию
zval
. Одним из главных нововведений стало то, что zval
больше не нужно отдельно размещать в куче. Также refcount теперь хранится не в самом zval
, а в любом из комплексных значений, на которые он указывает — в строках, массивах или объектах. Это даёт следующие преимущества:- Простые значения не требуют размещения в куче и не используют подсчёт ссылок.
- Больше нет никакого двойного подсчёта. В случае с объектами используется счётчик только внутри самого объекта.
- Поскольку refcount теперь хранится в самом значении, то оно может быть использовано независимо от самого zval. Например, строка может использоваться и в
zval
, и быть ключом в хэш-таблице. - Теперь стало гораздо меньше указателей, которые нужно перебрать, чтобы получить значение.
Вот как выглядит структура нового
zval
:struct _zval_struct {
zend_value value;
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type,
zend_uchar type_flags,
zend_uchar const_flags,
zend_uchar reserved)
} 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;
};
Первый компонент остался практически таким же, это объединение
value
. Второй компонент — целочисленный, хранящий информацию о типе, который с помощью объединения разбит на отдельные байты (можно игнорировать макрос ZEND_ENDIAN_LOHI_4
, он нужен лишь для обеспечения консистентной структуры между платформами с разными порядками следования байтов). Важными частями этой вложенной конструкции являются type
и type_flags
, о них я расскажу ниже.Также здесь есть одна небольшая проблема.
Value
занимает 8 байт, и благодаря своей структуре добавление даже одного байта повлечёт за собой увеличение размера zval
на 16 байт. Но ведь нам не нужно целых 8 байт для хранения типа. Поэтому в zval
есть дополнительное объединение u2
, которое по умолчанию не используется, но может применяться для хранения 4 байт данных. Разные компоненты объединения предназначены для разных видов использования этого дополнительного хранилища.В PHP 7 объединение
value
несколько отличается от пятой версии:typedef union _zend_value {
zend_long lval;
double dval;
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 {
ZEND_ENDIAN_LOHI(
uint32_t w1,
uint32_t w2)
} ww;
} zend_value;
Обратите внимание, что
value
теперь занимает 8 байт вместо 16. Оно хранит только целочисленные (lval
) и числа с плавающей запятой (dval
). Всё остальное — это указатель. Все типы указателей (за исключением специальных, отмеченных выше) используют подсчёт ссылок и содержат заголовок, определяемый zend_refcounted:struct _zend_refcounted {
uint32_t refcount;
union {
struct {
ZEND_ENDIAN_LOHI_3(
zend_uchar type,
zend_uchar flags,
uint16_t gc_info)
} v;
uint32_t type_info;
} u;
};
Конечно, в данной структуре имеется и refcount. Кроме того, здесь присутствуют
type
, flags
и gc_info
. Type всего лишь наследует тип zval
и позволяет GC различать разные подсчитываемые структуры без хранения в zval
. Flags
используется для различных задач с разными типами данных. О нём я расскажу подробнее во второй части.Gc_info
аналогичен buffered
в старой версии zval
. Но вместо хранения указателя на root buffer
он теперь хранит индекс. Поскольку root buffer
имеет ограниченную ёмкость (10 000 элементов), то достаточно использовать 16-битный указатель вместо 64-битного. Также в gc_info
содержится информация о «цвете» ноды, используемом для обозначения нод в коллекциях. Управление памятью zval
Я уже упоминал, что
zval
больше не нужно отдельно размещать в куче. Но их же нужно где-то хранить. Они всё ещё являются частью структур, размещаемых в кучах. Например, хэш-таблица будет содержать собственный zval
вместо указателя на отдельный zval
. Скомпилированная таблица переменных функции и таблица свойств объекта будут представлять собой zval
-массивы. В качестве таких zval
теперь обычно хранятся те, у которых косвенность на один уровень ниже. То есть zval
’ом теперь называется то, что раньше было zval
*.Когда-то нужно было копировать
zval
* и инкрементить его refcount, чтобы использовать zval
в новом месте. Теперь для этого достаточно скопировать содержимое zval
(игнорируя u2
) и, может быть, инкрементить refcount того значения, на которое он указывает, если значение использует подсчёт ссылок.Откуда PHP знает, что используется подсчёт? Это нельзя определить по одному лишь типу, поскольку некоторые типы не используют refcount — например, строки и массивы. Для этого используется один бит компонента
type_info
. Несколько бит также используется для кодирования свойств типа:
#define IS_TYPE_CONSTANT (1<<0) /* специальный */
#define IS_TYPE_IMMUTABLE (1<<1) /* специальный */
#define IS_TYPE_REFCOUNTED (1<<2)
#define IS_TYPE_COLLECTABLE (1<<3)
#define IS_TYPE_COPYABLE (1<<4)
#define IS_TYPE_SYMBOLTABLE (1<<5) /* специальный */
Три основных свойства, которые может иметь тип:
refcounted
, collectable
и copyable
.Collectable
означает, что zval
может быть частью цикла. Например, строковые переменные часто refcounted
, но создать с ними цикл невозможно.Сopyable
определяет, должно ли копироваться значение, когда выполняется дупликация. Если вы дуплицируете zval
, указывающий на массив, то это не означает, что всего лишь увеличится значение refcount массива. Вместо этого будет создана новая независимая копия массива. Но в случае с некоторыми типами, например, объектами и ресурсами, при дублировании всего лишь увеличивается refcount. Такие типы называются некопируемыми (non-copyable). Это соответствует передаче семантики объектов и ресурсов (которые не передаются по ссылке). Ниже приведена таблица, в которой показано, какие флаги могут использовать те или иные типы. Под «простыми» подразумеваются типы вроде целочисленных или булевых, которые не используют указатель на внешнюю структуру. Во второй части я также подробнее разберу неизменяемые массивы.
| refcounted | collectable | copyable | immutable
-----------------------+------------+-------------+----------+----------
Простые типы | | | |
Строковая | x | | x |
Интернированная строка | | | |
Массив | x | x | x |
Неизменяемый массив | | | | x
Объект | x | x | |
Ресурс | x | | |
Ссылка | x | | |
Давайте рассмотрим два примера того, как на практике работает управление
zval
. Для начала возьмём конструкцию с целочисленными значениями:$a = 42; // $a = zval_1(type=IS_LONG, value=42)
$b = $a; // $a = zval_1(type=IS_LONG, value=42)
// $b = zval_2(type=IS_LONG, value=42)
$a += 1; // $a = zval_1(type=IS_LONG, value=43)
// $b = zval_2(type=IS_LONG, value=42)
unset($a); // $a = zval_1(type=IS_UNDEF)
// $b = zval_2(type=IS_LONG, value=42)
Поскольку целочисленные значения больше не расшариваются, то обе переменные используют разные
zval
. Напоминаю, что они теперь встроены, а не размещены в памяти отдельно. Это подчёркивается и использованием = вместо ->. При очистке переменной тип соответствующего zval
изменится на IS_UNDEF
.Теперь второй пример, здесь уже используется сложное значение:
$a = []; // $a = zval_1(type=IS_ARRAY) -> zend_array_1(refcount=1, value=[])
$b = $a; // $a = zval_1(type=IS_ARRAY) -> zend_array_1(refcount=2, value=[])
// $b = zval_2(type=IS_ARRAY) ---^
// Здесь происходит разделение zval
$a[] = 1 // $a = zval_1(type=IS_ARRAY) -> zend_array_2(refcount=1, value=[1])
// $b = zval_2(type=IS_ARRAY) -> zend_array_1(refcount=1, value=[])
unset($a); // $a = zval_1(type=IS_UNDEF) и zend_array_2 уничтожен
// $b = zval_2(type=IS_ARRAY) -> zend_array_1(refcount=1, value=[])
Каждая переменная всё ещё имеет отдельный (встроенный)
zval
, но оба указателя ссылаются на одну и ту же (посчитанную) структуру zend_array
. После завершения изменения нужно продублировать массив. В PHP 5 в подобной ситуации всё работает аналогично.Типы
Какие типы поддерживаются в PHP 7:
// обычные типы данных
#define IS_UNDEF 0
#define IS_NULL 1
#define IS_FALSE 2
#define IS_TRUE 3
#define IS_LONG 4
#define IS_DOUBLE 5
#define IS_STRING 6
#define IS_ARRAY 7
#define IS_OBJECT 8
#define IS_RESOURCE 9
#define IS_REFERENCE 10
// константные выражения
#define IS_CONSTANT 11
#define IS_CONSTANT_AST 12
// внутренние типы
#define IS_INDIRECT 15
#define IS_PTR 17
В чём отличия от PHP 5:
- Тип
IS_UNDEF
используется вместо указателя наzval
NULL
(не путайте сIS_NULL zval
). Например, в приведённых выше примерах переменным назначается типIS_UNDEF
. - Тип
IS_BOOL
разделён наIS_FALSE
иIS_TRUE
. Поскольку такое булево значение теперь встроено в тип, это позволяет оптимизировать ряд проверок на основе типа. Данное изменение незаметно для пользователей, которые по прежнему оперируют единственным «булевым» типом. - PHP-ссылки больше не используют в zval флаг
is_ref
. Вместо него введён новый типIS_REFERENCE
. Ниже я расскажу, как это работает. IS_INDIRECT
иIS_PTR
являются специальными внутренними типами.
Тип
IS_LONG
вместо обычного long
из языка С теперь использует значение zend_long
. Причина в том, что в 64-битных Windows long
имеет разрядность только 32 бита. Поэтому PHP 5 больше не использует в Windows в обязательном порядке 32-битные числа. А в PHP 7 вы можете использовать 64-битные значения, если система также 64-битная. В следующей части мы подробнее рассмотрим реализацию индивидуальных типов
zend_refcounted
. Здесь же ограничимся разбором реализации PHP-ссылок.Ссылки
В PHP 7 кардинально изменился подход к использованию & PHP-ссылок. И это стало одной из основных причин появления багов. Для начала вспомним, как это реализовано в PHP 5. В обычной ситуации принцип «копирование при записи» подразумевает, что
zval
нужно продублировать, прежде чем вносить изменения. Это делается для того, чтобы случайно не изменить значение для каждого места, использующего zval
, что соответствует семантике передачи по значению.Для PHP-ссылок это не годится. Если значение является PHP-ссылкой, то вы сами захотите поменять его для каждого его пользователя. В PHP 5 флаг
is_ref
позволяет определить, является ли значение PHP-ссылкой, и если да, то требуется ли провести отделение перед внесением изменений. $a = []; // $a -> zval_1(type=IS_ARRAY, refcount=1, is_ref=0) -> HashTable_1(value=[])
$b =& $a; // $a, $b -> zval_1(type=IS_ARRAY, refcount=2, is_ref=1) -> HashTable_1(value=[])
$b[] = 1; // $a = $b = zval_1(type=IS_ARRAY, refcount=2, is_ref=1) -> HashTable_1(value=[1])
// Поскольку is_ref=1, PHP не будет отделять zval
С подобным подходом связана одна значительная проблема: невозможно расшаривать значение между двумя переменными, одна из которых является PHP-ссылкой, а другая нет.
$a = []; // $a -> zval_1(type=IS_ARRAY, refcount=1, is_ref=0) -> HashTable_1(value=[])
$b = $a; // $a, $b -> zval_1(type=IS_ARRAY, refcount=2, is_ref=0) -> HashTable_1(value=[])
$c = $b // $a, $b, $c -> zval_1(type=IS_ARRAY, refcount=3, is_ref=0) -> HashTable_1(value=[])
$d =& $c; // $a, $b -> zval_1(type=IS_ARRAY, refcount=2, is_ref=0) -> HashTable_1(value=[])
// $c, $d -> zval_1(type=IS_ARRAY, refcount=2, is_ref=1) -> HashTable_2(value=[])
// $d является ссылкой $c, а не $a или $b, поэтому zval должен быть скопирован сюда. Теперь у нас есть один zval с is_ref=0 и один с is_ref=1.
$d[] = 1; // $a, $b -> zval_1(type=IS_ARRAY, refcount=2, is_ref=0) -> HashTable_1(value=[])
// $c, $d -> zval_1(type=IS_ARRAY, refcount=2, is_ref=1) -> HashTable_2(value=[1])
// Поскольку у нас два отдельных zval $d[] = 1 не изменяет $a и $b.
Такое поведение приводит к тому, что при использовании ссылок производительность оказывается ниже, чем при использовании нормальных значений. Вот менее затейливый пример, иллюстрирующий данную проблему:
$array = range(0, 1000000);
$ref =& $array;
var_dump(count($array)); // <-- здесь происходит отделение
count()
принимает значение напрямую от переменной, но $array
является PHP-ссылкой, поэтому полная копия массива создаётся до того, как он передается в count()
. Если бы $array
не был ссылкой, то значение было бы расшарено.Теперь посмотрим, как PHP-ссылки реализованы в седьмой версии. Поскольку
zval
больше не выделены отдельно, то здесь невозможно использовать подход из PHP 5. Появился новый тип IS_REFERENCE
, использующий в качестве значения структуру zend_reference
:struct _zend_reference {
zend_refcounted gc;
zval val;
};
По сути,
zend_reference
представляет собой zval
с подсчётом ссылок. Во всех переменных набора ссылок zval
будет храниться с типом IS_REFERENCE
, указывающим на тот же самый инстанс zend_reference
. Поведение val
ничем не отличается от любого другого zval
, в том числе с точки зрения возможности расшаривания сложного значения, на которое он указывает. На вышеприведённых примерах рассмотрим семантику PHP 7. Для краткости возьмём только структуру, на которую ссылаются индивидуальные
zval
переменных.$a = []; // $a -> zend_array_1(refcount=1, value=[])
$b =& $a; // $a, $b -> zend_reference_1(refcount=2) -> zend_array_1(refcount=1, value=[])
$b[] = 1; // $a, $b -> zend_reference_1(refcount=2) -> zend_array_1(refcount=1, value=[1])
Новый
zend_reference
был создан присваиванием по ссылке. Обратите внимание, что у ссылки refcount равен 2 (потому что две переменные являются частью набора PHP-ссылок), но у самого значения refcount равен 1, поскольку на него ссылается одна структура zend_reference
. Теперь рассмотрим ситуацию, когда используются ссылки и не-ссылки: $a = []; // $a -> zend_array_1(refcount=1, value=[])
$b = $a; // $a, $b, -> zend_array_1(refcount=2, value=[])
$c = $b // $a, $b, $c -> zend_array_1(refcount=3, value=[])
$d =& $c; // $a, $b -> zend_array_1(refcount=3, value=[])
// $c, $d -> zend_reference_1(refcount=2) ---^
// Все переменные, как являющиеся PHP-ссылками, так и не являющиеся, используют один zend_array.
$d[] = 1; // $a, $b -> zend_array_1(refcount=2, value=[])
// $c, $d -> zend_reference_1(refcount=2) -> zend_array_2(refcount=1, value=[1])
// Только в данный момент происходит дупликация zend_array, после операции присваивания.
Важное различие между седьмой и пятой версиями заключается в том, что все переменные теперь могут использовать один и тот же массив, вне зависимости от того, являются ли они PHP-ссылками или нет. Массив будет отделён только после внесения некоторых изменений. То есть в PHP 7 можно безопасно передать в
count()
большой массив ссылку, и он не будет продублирован в памяти. Использование ссылок всё ещё уступает по производительности обычным значениям, поскольку они требуют размещения структуры zend_reference
и обычно обрабатываются движком не слишком эффективно.Заключение
Подведём итоги: главное нововведение в PHP 7 заключается в том, что
zval
больше не нужно выделять отдельно, и они больше не хранят refcount. Счётчики теперь хранятся внутри сложных значений, на которые они могут указывать — строки, массивы или объекты. Это снижает количество операций по выделению памяти, косвенность при пересчете и потребление памяти.
Secessus
Читают теги. Список хабов тоже читают.
И после прочтения возникает вопрос — почему в блоге mail.ru перевод статьи о PHP7?
Насколько понимаю, в компании используется Perl, Python, C++, Java на бэкенде.
iGusev
Не только, те же игры в их соцсетях по большей части на PHP бекенды делают (имхо)