PHP7系列:对zval的优化(三)

本文深入探讨了PHP7中zval结构体的变化,从24个字节减小到16个字节,以及内存分配策略的调整,不再使用MAKE_STD_ZVAL和ALLOC_ZVAL宏,减少了不必要的内存开销。通过实例展示了zval在不同数据类型下的内存占用,揭示了PHP7如何提高内存管理效率。
摘要由CSDN通过智能技术生成
zval结构体

PHP5的zval的定义:结构体的大小是(在64位系统)24个字节

struct _zval_struct {
     union {
          long lval;
          double dval;
          struct {
               char *val;
               int len;
          } str;
          HashTable *ht;
          zend_object_value obj;
          zend_ast *ast;
     } value;
     zend_uint refcount__gc;
     zend_uchar type;
     zend_uchar is_ref__gc;
};

PHP7的zval的定义: 全部都是联合体, 这个新的zval在64位环境下,现在只需要16个字节(2个指针size)

struct _zval_struct {
	zend_value        value;			/* value 根据u1de type来决定使用哪个类型*/
	union {
		struct {
			ZEND_ENDIAN_LOHI_3(
				zend_uchar    type,			/* active type 代表数据的类型*/ 
				zend_uchar    type_flags,
				union {
					uint16_t  extra;        /* not further specified */
				} u)
		} v;
		uint32_t type_info;
	} u1;
	union {
		uint32_t     next;                 /* hash collision chain 数组中hash冲突使用*/
		uint32_t     cache_slot;           /* cache slot (for RECV_INIT) */
		uint32_t     opline_num;           /* opline number (for FAST_CALL) */
		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 */
		uint32_t     access_flags;         /* class constant access flags */
		uint32_t     property_guard;       /* single property guard */
		uint32_t     constant_flags;       /* constant flags */
		uint32_t     extra;                /* not further specified */
	} u2;
};

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;

union u1 里面的type定义,代表的数据类型:

#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
static zend_always_inline zend_uchar zval_get_type(const zval* pz) {
	  return pz->u1.v.type;  //使用到了u1里面的type
}

#define Z_TYPE(zval)				zval_get_type(&(zval))   //获取定义变量的类型
#define Z_TYPE_P(zval_p)			Z_TYPE(*(zval_p))

可以使用gdb打个断点看看PHP内核的工作情况。

新建zval.php文件
<?php
$a = 2;
echo $a;
$b = 1.1;
echo $b;
$c = null;
echo $c;
$d = true;
echo $d;
$e = "string";
echo $e;

gdb php        
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-92.el6)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/codes/bin/php...done.
(gdb) b ZEND_ECHO_SPEC_CV_HANDLER    #断点位置
Breakpoint 1 at 0x9c24b9: file /root/php-7.2.31/Zend/zend_vm_execute.h, line 33112.
(gdb) r zval.php   #运行zval.php文件
Starting program: /home/codes/bin/php zval.php
[Thread debugging using libthread_db enabled]

Breakpoint 1, ZEND_ECHO_SPEC_CV_HANDLER (execute_data=0x7ffff601e030)
    at /root/php-7.2.31/Zend/zend_vm_execute.h:33112
warning: Source file is more recent than executable.
33112           USE_OPLINE
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.212.el6_10.3.x86_64 libxml2-2.7.6-21.el6_8.1.x86_64 nss-softokn-freebl-3.14.3-9.el6.x86_64 zlib-1.2.3-29.el6.x86_64
(gdb) n 
33117           z = _get_zval_ptr_cv_undef(opline->op1.var EXECUTE_DATA_CC);
(gdb) n
33119           if (Z_TYPE_P(z) == IS_STRING) {
(gdb) p z
$1 = (zval *) 0x7ffff601e080
(gdb) p *z
$2 = {value = {lval = 2, dval = 9.8813129168249309e-324, counted = 0x2, str = 0x2, 
    arr = 0x2, obj = 0x2, res = 0x2, ref = 0x2, ast = 0x2, zv = 0x2, ptr = 0x2, 
    ce = 0x2, func = 0x2, ww = {w1 = 2, w2 = 0}}, u1 = {v = {type = 4 '\004', 
      type_flags = 0 '\000', const_flags = 0 '\000', reserved = 0 '\000'}, 
    type_info = 4}, u2 = {next = 0, cache_slot = 0, lineno = 0, num_args = 0, 
    fe_pos = 0, fe_iter_idx = 0, access_flags = 0, property_guard = 0, extra = 0}}
(gdb) 

这里可以看到$a的赋值lval = 2, u1里面的type = 4,接着往下走:

(gdb) c
Continuing.
2
Breakpoint 1, ZEND_ECHO_SPEC_CV_HANDLER (execute_data=0x7ffff601e030)
    at /root/php-7.2.31/Zend/zend_vm_execute.h:33112
33112           USE_OPLINE
(gdb) n
33117           z = _get_zval_ptr_cv_undef(opline->op1.var EXECUTE_DATA_CC);
(gdb) n
33119           if (Z_TYPE_P(z) == IS_STRING) {
(gdb) p z
$3 = (zval *) 0x7ffff601e090
(gdb) p *z
$4 = {value = {lval = 4607632778762754458, dval = 1.1000000000000001, 
    counted = 0x3ff199999999999a, str = 0x3ff199999999999a, arr = 0x3ff199999999999a, 
    obj = 0x3ff199999999999a, res = 0x3ff199999999999a, ref = 0x3ff199999999999a, 
    ast = 0x3ff199999999999a, zv = 0x3ff199999999999a, ptr = 0x3ff199999999999a, 
    ce = 0x3ff199999999999a, func = 0x3ff199999999999a, ww = {w1 = 2576980378, 
      w2 = 1072798105}}, u1 = {v = {type = 5 '\005', type_flags = 0 '\000', 
      const_flags = 0 '\000', reserved = 0 '\000'}, type_info = 5}, u2 = {next = 0, 
    cache_slot = 0, lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0, 
    access_flags = 0, property_guard = 0, extra = 0}}

可以看到$b的type = 5 double类型,赋值则为dval = 1.1000000000000001
剩下的大家可以自己执行试试。

zval内存分配

PHP5的zval分配采用的是堆上分配内存:MAKE_STD_ZVAL和ALLOC_ZVAL宏。

本来一个zval只需要24个字节, 但是算上gc_info, 其实分配了32个字节,再加上PHP自己的内存管理在分配内存的时候都会在内存前面保留一部分信息(笔记里),从而导致实际上我们只需要24字节的内存, 但最后竟然分配48个字节之多。

PHP7开始, 移除了MAKE_STD_ZVAL/ALLOC_ZVAL宏, 不再支持存堆内存上申请zval。函数内部使用的zval要么来自外面输入, 要么使用在栈上分配的临时zval。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值