【转载】阅读PHP内核源码的心得

发表于2014-08-29 16:15  |  次阅读  |  0条评论  |   作者:admin

最近对于PHP内核源码的研读,在PHP的运行原理方面有了更深一步的了解。这个关于PHP运行原理的资料还是比较多的,而针对PHP空间使用的研究就比较少,决定试着分析一下PHP空间复杂度的问题。本文纯属作者的观点可能有很多错误,一切都是为了学习,发现错误尽请批评。

先从微观具体的角度分析PHP的内存使用

从微观的角度看,PHP在实际的运行当中,它的的内存无非就是主要分配在变量,函数,类,以及对象上(当然还有少部分用于其它),所以为了好理解,在阅读下去之前对几个小小的名称进行说明:变量池:就是PHP存储变量的内存堆。函数池:就是PHP存储函数信息的内存堆。类池:就是PHP存储类信息的内存堆。对象池:就是PHP存储对象信息的内存堆。

变量池

变量池里面装有多个类型的变量,那么它是怎么存储的呢?

当用户在PHP的脚本里定义变量时,PHP内核就会创建一个变量符号表,把变量的信息都放进这个变量符号表里面,也就是放进变量池。

PHP是使用以下这个数据结构对变量进行存储的


typedef struct _zval_struct zval;
struct _zval_struct {
    /* Variable information */
    zvalue_value value;     /* value */
    zend_uint refcount__gc;
    zend_uchar type;    /* active type */
    zend_uchar is_ref__gc;
};

结构体里面的is_ref__gc表示变量是否被引用,默认值为0

refcount__gc表示变量被引用的次数,默认值为1,讲到这里就联想到PHP的Copy-On-While机制,当变量$a被赋值给变量$b时,php内核是不会马上把$a复制一份然后给$b,而是把$a的refcount__gc加1,在$b变量被改变时,才真正复制一份给$b。如果是$b=&$a的话,那就是马上复制一份给$b了,所以PHP是不提倡用&符的。type指的就是变量的具体类型,所以PHP实际上是有类型的,并不是说定义的时候不用指明类型,它就没有类型了。

zvalue_value是一个联合体,

ypedef 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;

这里设计得很巧妙,很好的利用了联合体与结构体的区别,联合体里面的成员是共用一个地址空间的,而结构体里面的每一个成员都有自己的地址空间的。

在_zvalue_value里面还有个HashTable的 *ht,这个是用来存储数组类型的。哈希表是一种高效的键值对应的数据结构,在PHP里面有两种对哈希表的实现,他们分别是HashTable和Bucket,在《深入理解PHP内核》这本书里面比多的阐述了哈希表的优势,在此我想说一点哈希表的缺点,哈希表要开辟一片内存来存储键值,增加了内存开销。

函数池

a)用户函数

PHP内核把函数的函数体存储在一个*function指针指向一个名为zend_function的结构体中:

typedef union _zend_function {
    zend_uchar type;    /* MUST be the first element of this struct! */
    struct {
        zend_uchar type;  /* never used */
        char *function_name;    //函数名称
        zend_class_entry *scope; //函数所在的类作用域
        zend_uint fn_flags;    
        union _zend_function *prototype; //函数原型
        zend_uint num_args;     //参数数目
        zend_uint required_num_args; //需要的参数数目
        zend_arg_info *arg_info;  //参数信息指针
        zend_bool pass_rest_by_reference;
        unsigned char return_reference;  //返回值
    } common;
    zend_op_array op_array;   //函数中的操作
    zend_internal_function internal_function; 
} zend_function;

zend_function的结构中的op_array存储了该函数中所有的操作,当函数被调用时,ZE就会将这个op_array中的opline一条条顺次执行,并将最后的返回值返回,虽然表面看起来在定义PHP函数时返回值可有可无,但实际上都是有返回值,PHP内核会帮忙返回一个NULL值

b)内部函数

内部函数的数据结构:

typedef struct _zend_internal_function {
    /* Common elements */
    zend_uchar type;
    char * function_name;
    zend_class_entry *scope;
    zend_uint fn_flags;
    union _zend_function *prototype;
    zend_uint num_args;
    zend_uint required_num_args;
    zend_arg_info *arg_info;
    zend_bool pass_rest_by_reference;
    unsigned char return_reference;    /* END of common elements */
    void (*handler)(INTERNAL_FUNCTION_PARAMETERS);
    struct _zend_module_entry *module;
} zend_internal_function;

内部函数通过zend_register_functions进行注册,然后把函数的信息存进函数池中(函数池本质就是一个哈希结构的表),然后ZE再调用zend_execute_internal通过zend_internal_function_handler来执行这个函数。

PHP对变量函数的支持,也算是对C/C++的一个超越吧,PHP当发现变量后面有括号,就会先在变量池寻找变量,获取到变量的值,然后转到函数池寻找该值的函数名,找到了就执行

d)匿名函数

匿名函数是在PHP5.3才开始支持的,匿名函数虽然是匿名的但是在函数池里面还是有唯一的标识来确认他们的。
函数池里面就是放着一系列有着唯一标识的函数,通过函数名字就可以直接调用了。不用初始化,也不用实例化,所以还是很方便的。

类池

类本身既是一系列的变量和方法的集合。类的思想在C语言里面就体现在结构体struct的思想,只不过struct没有分public,protected,以及private。类在PHP里存储的数据结构:

struct _zend_class_entry {
…//太多了省略一部分
HashTable function_table;
HashTable default_properties;          // 默认属性
    HashTable properties_info;     // 属性信息
    HashTable default_static_members;// 类本身所具有的静态变量
    HashTable *static_members; // type == ZEND_USER_CLASS时,取&default_static_members;
    /* 魔术方法 */
    union _zend_function *__get;
      ……. //太多了省略一部分
    char *doc_comment;
    zend_uint doc_comment_len;
    struct _zend_module_entry *module; // 类所在的模块入口:EG(current_module)
};

对象池

类是虚的,而对象就是类实例化出来的实体。之所以把类和对象分开来讲是因为类就像树的根节点,而对象是子节点,是一对多的关系。

对象在PHP里的储存结构:
typedef struct _zend_object_value {
zend_object_handle handle;
 //  unsigned int类型,EG(objects_store).object_buckets的索引
    zend_object_handlers *handlers;
} zend_object_value;
PHP内核会将所有的对象放在对象池中,本质就是对象列表容器,在需要时,PHP内核会根据handle索引从对象列表中获取相对应的对象。而获取的对象有其独立的结构

typedef struct _zend_object {
    zend_class_entry *ce;
    HashTable *properties;
    HashTable *guards; /* protects from __get/__set ... recursion */
} zend_object;

其中ce就是指向类结构的,也就是指向类池的指针。properties是放对象自己的属性的。对象池就是放着一个个的zend_object,它们各自都有一根指针指向自己的类。也就是对象池附属于类池,当类池被释放时,对象池也应该被回收。

从宏观的角度分析PHP的内存使用

从宏观的角度分析,PHP内核所做的空间内存工作无非就是分配内存与回收内存,内存池:就是PHP有效使用的内存堆。垃圾池:就是PHP不再使用的内存或者被废弃的内存堆。

内存池

从宏观的角度分析,PHP内核所做的空间内存工作无非就是分配内存与回收内存,内存池:就是PHP有效使用的内存堆。垃圾池:就是PHP不再使用的内存或者被废弃的内存堆。

从更具体的角度来讲PHP内存管理的话,可以说PHP中的内存管理主要工作就是维护三个列表:小块内存列表(free_buckets)(272bytes)、大块内存列表(large_free_buckets)(512bytes)和剩余内存列表(rest_buckets)。这里的三个表都是哈希结构的表。小块内存表采用的是邻接表的数据结构,大块内存列表的数据结构就比较复杂,我找不到准确的名称来形容它了,在《深入理解PHP内核》那本书有详细的图解。

垃圾池

PHP有自己专门的垃圾回收机制,它的存储结构体:

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 :z是为了确保它和它所指向的zval变量分配的内存的地址一致。u包括gc_root_buffer结构的buffered字段和zval_gc_info结构的next字段。这两个字段一个是表示垃圾收集机制缓存的根结点,一个是zval_gc_info列表的下一个结点。有了这个zval_gc_info链表,垃圾回收变得比较简单可行了。而且变量结构体里面有is_ref__gc和refcount变量,类有refcount变量,那就更好的判断该段内存是否可以回收了。据说PHP内存回收时1ms执行一次,也算挺及时的了,再高就会影响PHP的执行效率了。

以上讲的内存池,听起来还比较抽象,我想一种比较好理解的方式来表达PHP内存的使用情况。如图:

以上讲的内存池,听起来还比较抽象,我想一种比较好理解的方式来表达PHP内存的使用情况。如图:

补充:PHP运行原理

前面的两大点都是讲PHP内存,所以理解起来比较困难,所以也顺便说说我对PHP运行原理的理解,主要是画图来表达:

以上就是我认为的PHP运行原理,纯属个人看法,可能有错误

本站关键字:sunny90 web开发 数据库 移动开发 服务器 Nginx Mysql PHP
Copyright © sunny90版权所有 power by sunny90.com  
湘ICP备14012284号-1,粤公网安备 44030602000307号