Memory Optimizations in PHP7: Changes to zval and HashTable Structures
The article analyzes PHP7's memory optimizations by comparing the zval and HashTable structures of PHP 5.3 and PHP 7.2, showing how reduced sizes and better cache‑line alignment lead to significant performance improvements.
After reviewing memory fundamentals, the article examines practical PHP7 optimizations, starting with the evolution of the zval structure from PHP 5.3 to PHP 7.2.
In PHP 5.3, the 64‑bit _zval_struct consists of a zvalue_value union (12 bytes) plus refcount, type, and is_ref fields, totaling 22 bytes; after alignment it occupies 24 bytes.
typedef unsigned int zend_object_handle;
typedef struct _zend_object_value {
zend_object_handle handle;
zend_object_handlers *handlers;
} zend_object_value;
typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
} zvalue_value;
struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */
zend_uint refcount__gc;
zend_uchar type; /* active type */
zend_uchar is_ref__gc;
};PHP 7.2 redesigns the zval as a compact zend_value union (8 bytes) and a 4‑byte type info field, reducing the whole _zval_struct to 16 bytes.
typedef struct _zval_struct zval;
typedef union _zend_value {
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;
} zend_value;
struct _zval_struct {
zend_value value; /* value */
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type,
zend_uchar type_flags,
zend_uchar const_flags,
zend_uchar reserved)
} v;
int type_info;
} u1;
union { ...... } u2;
};The article then compares the HashTable layout. In PHP 5.3 the struct contains multiple 4‑byte integers, several 8‑byte pointers, and small flags, resulting in 67 bytes of raw fields and 72 bytes after 8‑byte alignment.
typedef struct _hashtable {
uint nTableSize;
uint nTableMask;
uint nNumOfElements; // note: wasteful
ulong nNextFreeElement;
Bucket *pInternalPointer; /* Used for element traversal */
Bucket *pListHead;
Bucket *pListTail;
Bucket **arBuckets;
dtor_func_t pDestructor;
zend_bool persistent;
unsigned char nApplyCount;
zend_bool bApplyProtection;
} HashTable;PHP 7.2’s HashTable is streamlined: a reference‑counted header (8 bytes), a 4‑byte flag union, several 4‑byte integers, and pointer fields, summing to 56 bytes, which fits exactly into a single 64‑byte cache line.
typedef struct _zend_array HashTable;
struct _zend_array {
zend_refcounted_h gc;
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar flags,
zend_uchar nApplyCount,
zend_uchar nIteratorsCount,
zend_uchar consistency)
} v;
uint32_t flags;
} u;
uint32_t nTableMask;
Bucket *arData;
uint32_t nNumUsed;
uint32_t nNumOfElements;
uint32_t nTableSize;
uint32_t nInternalPointer;
zend_long nNextFreeElement;
dtor_func_t pDestructor;
};Beyond raw size reduction, the author explains two deeper performance benefits: (1) a 56‑byte structure fits in one cache‑line fetch versus two for the 72‑byte version, effectively doubling memory‑access speed; (2) halving the size doubles the number of tables that can reside in the fixed‑size CPU caches, greatly improving hit rates.
Thus, the seemingly modest byte‑level changes in PHP’s core data structures translate into substantial runtime performance gains.
Refining Core Development Skills
Fei has over 10 years of development experience at Tencent and Sogou. Through this account, he shares his deep insights on performance.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.