深入理解PHP7内核之OBJECT

栏目: IT技术 · 发布时间: 4年前

内容简介:前面的几篇,我系统的介绍了PHP7以后的按照惯例,我先带大家回顾下PHP5时的zend_object(此部分内容之前的文章中也有涉及,如果熟悉可以跳过):zend_object(以下简称object)在PHP5中其实是一种相对特殊的存在, 在PHP5中,只有resource和object是引用传递,也就是说在赋值,传递的时候都是传递的本身,也正因为如此,Object和Resource除了使用了Zval的引用计数以外,还采用了一套独立自身的计数系统。

前面的几篇,我系统的介绍了 PHP 7以后的 Zval , Hashtable(zend_array) , 以及 Reference , 今天我来讲讲zend_object。

PHP5

按照惯例,我先带大家回顾下PHP5时的zend_object(此部分内容之前的文章中也有涉及,如果熟悉可以跳过):

typedef struct _zend_object {
    zend_class_entry *ce;
    HashTable *properties;
    zval **properties_table;
    HashTable *guards;
} zend_object;

zend_object(以下简称object)在PHP5中其实是一种相对特殊的存在, 在PHP5中,只有resource和object是引用传递,也就是说在赋值,传递的时候都是传递的本身,也正因为如此,Object和Resource除了使用了Zval的引用计数以外,还采用了一套独立自身的计数系统。

这个我们从zval中也能看出object和其他的类似字符串的的不同:

typedef union _zvalue_value {
    long lval;
    double dval;
    struct {
        char *val;
        int len;
    } str;
    HashTable *ht;
    zend_object_value obj;
} zvalue_value;

对于字符串和数组,zval中都直接保存它们的指针,而对于object却是一个zend_object_value的结构体:

typedef unsigned int zend_object_handle;

typedef struct _zend_object_value {
    zend_object_handle handle;
    const zend_object_handlers *handlers;
} zend_object_value;

真正获取对象是需要通过这个zend_object_handle,也就是一个int的索引去全局的object buckets中查找:

ZEND_API void *zend_object_store_get_object_by_handle(zend_object_handle handle TSRMLS_DC)
{
    return EG(objects_store).object_buckets[handle].bucket.obj.object;
}

而EG(objects_store).object_buckets则是一个数组,保存着:

typedef struct _zend_object_store_bucket {
    zend_bool destructor_called;
    zend_bool valid;
    zend_uchar apply_count;
    union _store_bucket {
        struct _store_object {
            void *object;
            zend_objects_store_dtor_t dtor;
            zend_objects_free_object_storage_t free_storage;
            zend_objects_store_clone_t clone;
            const zend_object_handlers *handlers;
            zend_uint refcount;
            gc_root_buffer *buffered;
        } obj;
        struct {
            int next;
        } free_list;
    } bucket;
} zend_object_store_bucket;

其中,zend_object_store_bucket.bucket.obj.object才保存着真正的zend_object的指针,注意到此处是void *, 这是因为我们很多扩展的自定义对象,也是可以保存在这里的。

另外我们也注意到zend_object_store_bueckt.bucket.obj.refcount, 这个既是我刚刚讲的object自身的引用计数,也就是zval有一套自己的引用计数,object也有一套引用计数。

<?php
$o1 = new Stdclass();
//o1.refcount == 1, object.refcount == 1
$o2 = $o1;
//o1.refcount == o2.refcoun == 2; object.refcount = 1;
$o3 = &$o2;
//o3.isref == o2.isref==1
//o3.refcount == o2.refcount == 2
//o1.isref == 0; o1.refcount == 1
//object.refcount == 2

这样,可以让object可以保证不同于普通的zval的COW机制,可以保证object可以全局传引用。

可见,从一个zval到取到实际的object,我们需要首先获取zval.value.obj.handle, 然后拿着这个索引再去EG(objects_store)查询,效率比较低下。

对于另外一个常见的操作,就是获取一个zval对象的类的时候,我们甚至需要调用一个函数:

#define Z_OBJCE(zval) zend_get_class_entry(&(zval) TSRMLS_CC)

PHP7

到了PHP7,如我前面的文章 深入理解PHP7内核之ZVAL 所说, zval中直接保存了zend_object对象的指针:

struct _zend_object {
    zend_refcounted_h gc;
    uint32_t          handle;
    zend_class_entry *ce;
    const zend_object_handlers *handlers;
    HashTable        *properties;
    zval              properties_table[1];
};

而EG(objects_store)也只是简单的保存了一个zend_object**等指针:

typedef struct _zend_objects_store {
    zend_object **object_buckets;
    uint32_t top;
    uint32_t size;
    int free_list_head;
} zend_objects_store;

而对于前面的COW的例子,对于IS_OBJECT来说, 用IS_TYPE_COPYABLE来区分,也就是,当发生COW的时候,如果这个类型没有设置 IS_TYPE_COPYABLE,那么就不会发生“复制".

#define IS_ARRAY_EX  (IS_ARRAY | ((IS_TYPE_REFCOUNTED | IS_TYPE_COLLECTABLE | IS_TYPE_COPYABLE) << Z_TYPE_FLAGS_SHIFT))
#define IS_OBJECT_EX (IS_OBJECT | ((IS_TYPE_REFCOUNTED | IS_TYPE_COLLECTABLE) << Z_TYPE_FLAGS_SHIFT))

如上,大家可以看到对于ARRAY来说定义了IS_TYPE_REFCOUNTED, IS_TYPE_COLLECTABLE和IS_TYPE_COPYABLE, 但是对于OBJECT, 则缺少了IS_TYPE_COPYABLE.

在SEPARATE_ZVAL中:

#define SEPARATE_ZVAL(zv) do {                          \
        zval *_zv = (zv);                               \
        if (Z_REFCOUNTED_P(_zv) ||                      \
            Z_IMMUTABLE_P(_zv)) {                       \
            if (Z_REFCOUNT_P(_zv) > 1) {                \
                if (Z_COPYABLE_P(_zv) ||                \
                    Z_IMMUTABLE_P(_zv)) {               \
                    if (!Z_IMMUTABLE_P(_zv)) {          \
                        Z_DELREF_P(_zv);                \
                    }                                   \
                    zval_copy_ctor_func(_zv);           \
                } else if (Z_ISREF_P(_zv)) {            \
                    Z_DELREF_P(_zv);                    \
                    ZVAL_DUP(_zv, Z_REFVAL_P(_zv));     \
                }                                       \
            }                                           \
        }                                               \
    } while (0)

如果不是Z_COPYABLE_P, 那么就不发生写时分离。

这里有的同学会问,那既然已经在zval中直接保存了zend_object*了,那为啥还需要EG(objects_store)呢?

这里有2个主要原因:

  • 1. 我们需要在PHP请求结束的时候保证所有的对象的析构函数都被调用,因为object存在循环引用的情况,那如何快速的便利所有存活的对象呢? EG(objects_store)是一个很不错的选择。
  • 2. 在PHPNG开发点时候,为了保证最大向后兼容,我们还是需要保证获取一个对象的handle的接口, 并且这个handle还是要保证原有的语义。

但实际上来说呢, 其实EG(objects_store)已经没啥太大的用处了, 我们是可以在将来去掉它的。

好,接下来出现了另外一个问题,我们再看看zend_object的定义, 注意到末尾的properties_table[1], 也就是说,我们现在会把object的属性跟对象一起分配内存。 也就是说zend_object这个结构体现在是可能变长的。

那在当时写PHPNG的时候就给我带来了一个问题, 在PHP5时代,很多的自定义对象是这么定义的:

typedef struct _mysqli_object {
    zend_object         zo;
    void                *ptr;
    HashTable           *prop_handler;
} mysqli_object; /* extends zend_object */

也就是说zend_object都在自定义的内部类的头部,这样当然有一个好处是可以很方便的做cast, 但是因为目前zend_object变成变长了,并且更严重的是你并不知道用户在PHP继承了你这个类以后,他新增了多少属性的定义。

于是没有办法,在写PHPNG的时候,我做了大量的调整如下:

typedef struct _mysqli_object {
    void                *ptr;
    HashTable           *prop_handler;
    zend_object         zo;
} mysqli_object; /* extends zend_object */

相对的,再新增定义:

static inline mysqli_object *php_mysqli_fetch_object(zend_object *obj) {
    return (mysqli_object *)((char*)(obj) - XtOffsetOf(mysqli_object, zo));
}

这样以来就规避了这个问题, 而在实际的分配自定义对象的时候,我们也需要采用如下的方法:

obj = ecalloc(1, sizeof(mysqli_object) + zend_object_properties_size(class_type));

这块,大家在写扩展的时候,如果用到自定义的类,一定要注意。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

scikit learn机器学习

scikit learn机器学习

黄永昌 / 机械工业出版社 / 2018-3-1 / CNY 59.00

本书通过通俗易懂的语言、丰富的图示和生动的实例,拨开了笼罩在机器学习上方复杂的数学“乌云”,让读者以较低的代价和门槛轻松入门机器学习。本书共分为11章,主要介绍了在Python环境下学习scikit-learn机器学习框架的相关知识。本书涵盖的主要内容有机器学习概述、Python机器学习软件包、机器学习理论基础、k-近邻算法、线性回归算法、逻辑回归算法、决策树、支持向量机、朴素贝叶斯算法、PCA ......一起来看看 《scikit learn机器学习》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码