Backend Development 7 min read

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.

Refining Core Development Skills
Refining Core Development Skills
Refining Core Development Skills
Memory Optimizations in PHP7: Changes to zval and HashTable Structures

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.

BackendMemory OptimizationPHPHashTablezval
Refining Core Development Skills
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.