PHP5
いつものように、最初に PHP5 の zend_object を確認します (この部分の前の内容)この記事でも取り上げています (詳しい方は読み飛ばしていただいても構いません)。以前に興味があった場合は、私が 10 年前に書いた PHP 原理を深く理解するためのオブジェクトも読むことができます。
PHP5 では、オブジェクトの定義は次のとおりです:
typedef struct _zend_object { zend_class_entry *ce; HashTable *properties; zval **properties_table; HashTable *guards; } zend_object;
<?php class Foo { public $a = 'defaul property'; } $a = New Foo(); $a->b = 'dynamic property';
typedef union _zvalue_value { long lval; double dval; struct { char *val; int len; } str; HashTable *ht; zend_object_value obj; } zvalue_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_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; }
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;
<?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
#define Z_OBJCE(zval) zend_get_class_entry(&(zval) TSRMLS_CC)
PHP7
When it PHP7 カーネル ZVAL の詳細な理解に関する前回の記事で述べたように、Zval は PHP7 に登場し、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]; };
typedef struct _zend_objects_store { zend_object **object_buckets; uint32_t top; uint32_t size; int free_list_head; } zend_objects_store;
#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))
#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)
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 中国語 Web サイトの他の関連記事を参照してください。