PHP7 内核 Object 深入理解-PHP7

资源魔 38 0

PHP5

依照常规,我先带各人回顾下PHP5时的zend_object(此局部内容以前的文章中也有触及,假如相熟能够跳过), 以前假如有兴味也能够看看我10年前写的深化了解PHP原理之工具.

PHP5中,工具的界说以下:

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

此中ce存储了这个工具所属的类, 对于properties_table以及properties, properties_table是声明的属性,properties是静态属性,也就是比方:

<?php
class Foo {
    public $a = 'defaul property';
}
$a = New Foo();
$a->b = 'dynamic property';

由于正在Foo的界说中,咱们声明了public $a, 那末$a就是已知的声明属性,它的可见性,包罗正在properties_table中存储的地位都是正在声明后就确定的。

而$a->b, 是咱们静态给增加的属性,它没有属于曾经声明的属性,这个会存储正在properties中。

其实从类型上也能看进去, properties_table是zval*的数组,而properties是Hashtable。

guards次要用正在魔术办法挪用的时分嵌套维护, 比方__isset/__get/__set。

总体来讲, 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时代,不少的自界说工具是这么界说的(mysqli为例):

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 */

也就是把zend_object从头部,挪到了尾部,那为了能够从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));

这块,各人正在写扩大的时分,假如用到自界说的类,肯定要留意。

而以前正在PHP5中的guard, 咱们也晓得并非一切的类城市声明魔术办法,正在PHP5中把guard放正在object中会正在年夜局部状况下都是糜费内存, 以是正在PHP7中会,咱们会依据一个类能否声明了魔术办法(IS_OBJ_HAS_GUARDS)来决议要没有要调配,而详细的调配中央也放正在了properties_table的末尾:

if (GC_FLAGS(zobj) & IS_OBJ_HAS_GUARDS) {
        guards = Z_PTR(zobj->properties_table[zobj->ce->default_properties_count]);
....
}

从而能够正在年夜局部状况下,节流一个指针的内存调配。

最初就是, PHP7中正在取一个工具的类的时分,就会十分不便了, 间接zvalu.value.obj->ce便可,一些类所自定的handler也就能够很便捷的拜访到了, 功能晋升显著。

保举教程:《PHP7》《PHP教程》

以上就是PHP7 内核 Object 深化了解的具体内容,更多请存眷资源魔其它相干文章!

标签: php PHP7 php7开发教程 php7开发资料 php7开发自学

抱歉,评论功能暂时关闭!