php object type,深入理解 PHP7 内核之 OBJECT

typedefstruct_ zend_object_value{zend_object_handle handle;constzend_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){returnEG(objects_store).object_buckets[handle].bucket.obj. object;}

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

typedefstruct _zend_object_store_bucket {zend_booldestructor_called;zend_boolvalid;zend_ucharapply_count;union_store_bucket {struct_store_object {void*object;zend_objects_store_dtor_tdtor;zend_objects_free_object_storage_tfree_storage;zend_objects_store_clone_tclone;constzend_object_handlers *handlers;zend_uintrefcount;gc_root_buffer*buffered;}obj;struct{intnext;}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也有一套引用计数。

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

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

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

# defineZ_OBJCE(zval) zend_get_class_entry(&(zval) TSRMLS_CC)

PHP7

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

struct_zend_object {zend_refcounted_hgc;uint32_thandle;zend_class_entry*ce;constzend_object_handlers *handlers;HashTable*properties;zvalproperties_table[1];};

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

typedefstruct_ zend_objects_store{zend_object **object_buckets;uint32_ttop;uint32_tsize;intfree_list_head;} zend_objects_store;

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

# defineIS_ARRAY_EX (IS_ARRAY | ((IS_TYPE_REFCOUNTED | IS_TYPE_COLLECTABLE | IS_TYPE_COPYABLE) << Z_TYPE_FLAGS_SHIFT))# defineIS_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为例):

typedefstruct_ mysqli_object{zend_object zo;void*ptr;HashTable *prop_handler;} mysqli_object; /* extends zend_object */

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

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

typedefstruct_ mysqli_object{void*ptr;HashTable *prop_handler;zend_object zo;} mysqli_object; /* extends zend_object */

也就是把zend_object从头部,挪到了尾部,那为了可以从zend_object取得自定义对象,我们需要新增定义:

staticinlinemysqli_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));

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

现在再取一个对象的类的时候,就会非常方便了, 直接zvalu.value.obj->ce即可,一些类所自定的handler也就可以很便捷的访问到了, 性能提升明显。返回搜狐,查看更多

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值