商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > PHP7的zval结构是如何实现内存优化的

PHP7的zval结构是如何实现内存优化的

  发布于2026-07-15 阅读(0)

扫一扫,手机访问

PHP 7 在内存管理上做的最核心的一件事,就是把 zval 结构给“砍”了一刀,直接从 PHP 5 时代的 24 字节压缩到了 16 字节。别小看这 8 个字节的差距,在动辄百万级变量的现代 Web 应用中,这能省下海量的内存。那么,这个“瘦身”魔术是怎么变的?

zval 从 24 字节压缩到 16 字节,靠的是结构重排和 union 复用

先说说 PHP 5 的问题。在 64 位系统下,PHP 5 的 zval 实际占用了 24 字节。罪魁祸首是它内部的 zvalue_value 联合体,里面最大成员(比如 zend_object_value)为了对齐,拉高了整个结构的尺寸。再加上独立的 refcount__gcis_ref__gc 字段,冗余自然就大了。

PHP 7 的思路很直接:把这些字段“压扁”进两个 8 字节的块里,总共 16 字节。具体来说,就是拆成 value(8 字节联合体)和 u1+u2(共 8 字节)两个部分。关键动作有三个:

  • 引用计数不再住在 zval 里了,而是下沉到具体的数据结构(比如 zend_stringzend_array)自己管。zval 只负责存个指针或者原生值,轻装上阵。
  • 类型标识 typetype_flags 合并进 u1.v.type 这一个字节里,用单字节就能区分 IS_LONGIS_STRING 等十多种类型,不再需要额外字段。
  • value 联合体里,去掉了 PHP 5 中冗余的 str.len 这类内嵌字段,字符串长度这种信息,交给 zend_string 结构自己去管理。

小类型直接存值,大类型只存指针,避免无谓内存分配

接下来看看第二个设计思路:能存直接值的,绝不多绕一层。PHP 7 的 zend_value 联合体决定了“什么该放进去,什么该甩出去”。整数、布尔、浮点这些“小个子”数据,直接塞进 8 字节的 value 空间里;而字符串、数组、对象这类“大块头”,zval 里只存一个指针(比如 zend_string *),真实数据另起内存块。

这意味着:

  • $a = 42zval.value.lval 直接存 42,零额外分配。
  • $b = "hello"zval.value.str 指向一块独立的 zend_string 内存,里面包含 lenh(hash 缓存)、val[] 等。
  • unset($b) 后,zval 类型变成 IS_UNDEF,但背后的 zend_string 不立即释放——等 refcount 降为 0 才真回收。

栈上预分配 zval,减少高频 malloc/free 开销

PHP 5 里,每次创建变量都要调 MAKE_STD_ZVAL() 从堆上 malloc 一块内存,这在高频场景下开销巨大。PHP 7 改成了在函数栈帧里直接声明 zval val,或者批量预分配一组 zval 池(比如 VM 执行时的 CV 变量表)。这省掉了大量系统调用和堆管理碎片。

有几个实操细节值得注意:

  • 扩展开发中写 zval val; 是安全的,但若需长期持有(比如存在全局哈希表里),必须确保它指向的数据(如 zend_string)有正确 refcount。
  • ZVAL_COPY() 不是简单 memcpy,它会递增被拷贝对象的 refcount,并处理 IS_REFERENCE 等特殊标记。
  • 直接赋值 zval new_val = old_val; 是浅拷贝,仅复制 16 字节结构,不碰背后数据——这是性能关键,也是引用逻辑的起点。

IS_UNDEF 和 IS_INDIRECT 类型让运行时更轻量

PHP 7 新增了两个“幽灵”类型:IS_UNDEF 表示“已 unset 但尚未清理的槽位”,IS_INDIRECT 用于间接引用(如全局符号表里的 CV 变量)。它们不携带实际数据,zval.value 字段完全闲置,却能避免内存重排或 bucket 删除开销。

典型场景:

  • 数组 unset($arr['key']) 后,对应 bucket 的 zval.u1.v.type 设为 IS_UNDEF,后续 foreach 自动跳过,count($arr) 也不计入。
  • 函数参数绑定时,IS_INDIRECT zvalvalue.zv 指向真正变量,实现“变量的变量”语义,不用每次都查符号表。
  • 这两种类型的存在,使得 hashtable 的 rehash 可以延迟执行,降低突发性内存抖动。

真正难啃的地方不在结构定义,而在 refcount 与类型标记的耦合时机——比如 ZVAL_DEREF() 何时触发解引用、zval_ptr_dtor() 怎么判断是否该释放底层数据。这些边界行为不看源码很容易踩空。

本文转载于:https://www.php.cn/faq/2824758.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注