Home  >  Article  >  Backend Development  >  In-depth understanding of PHP7 kernel Object

In-depth understanding of PHP7 kernel Object

Guanhui
Guanhuiforward
2020-05-15 11:10:053727browse

In-depth understanding of PHP7 kernel Object

PHP5

As usual, I will first take you to review zend_object in PHP5 (the content before this part It is also covered in the article (you can skip it if you are familiar with it). If you are interested before, you can also read the objects for in-depth understanding of PHP principles that I wrote 10 years ago.

In PHP5, the definition of objects is as follows:

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

where ce stores the class to which this object belongs. Regarding properties_table and properties, properties_table is a declared property, and properties is a dynamic property, that is, for example:

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

Because in the definition of Foo, we declare public $a, then $a is a known declared property, and its visibility, including the location stored in the properties_table, is determined after declaration.

And $a->b is a property we add dynamically. It does not belong to the declared properties. This will be stored in properties.

In fact, it can be seen from the type that properties_table is an array of zval*, and properties is a Hashtable.

Guards are mainly used for nested protection when calling magic methods, such as __isset/__get/__set.

Generally speaking, zend_object (hereinafter referred to as object) is actually a relatively special existence in PHP5. In PHP5, only resource and object are passed by reference, which means that when assigning and passing, It is the transfer itself. Because of this, in addition to using Zval's reference counting, Object and Resource also use an independent counting system.

We can also see the difference between object and other similar strings from zval:

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

For strings and arrays, zval directly stores their pointers, while for object is a zend_object_value structure:

typedef unsigned int zend_object_handle;

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

To actually obtain the object, you need to search in the global object buckets through this zend_object_handle, which is an int index:

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

And EG(objects_store).object_buckets is an array, saving:

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;

Among them, zend_object_store_bucket.bucket.obj.object saves the real zend_object pointer, notice this There is void * everywhere. This is because many of our extended custom objects can also be saved here.

In addition, we also noticed that zend_object_store_bueckt.bucket.obj.refcount, this is the reference count of the object itself that I just talked about, that is, zval has its own reference count, and object also has its own reference count.

<?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

In this way, the object can ensure that the COW mechanism is different from the ordinary zval, and can ensure that the object can be referenced globally.

It can be seen that from a zval to get the actual object, we need to first obtain the zval.value.obj.handle, and then use this index to query EG (objects_store), which is relatively inefficient.

For another common operation, when obtaining the class of a zval object, we also need to call a function:

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

PHP7

When it comes to PHP7, as I said in my previous article on in-depth understanding of PHP7 kernel ZVAL, zval directly saves the pointer of the zend_object 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];
};

And EG (objects_store) simply saves one zend_object** and other pointers:

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

For the previous COW example, for IS_OBJECT, IS_TYPE_COPYABLE is used to distinguish it. That is, when COW occurs, if this type does not set IS_TYPE_COPYABLE, then it will not "Copying" will occur.

#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))

As above, you can see that IS_TYPE_REFCOUNTED, IS_TYPE_COLLECTABLE and IS_TYPE_COPYABLE are defined for ARRAY, but for OBJECT, IS_TYPE_COPYABLE is missing.

In 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)

If it is not Z_COPYABLE_P, then write-time separation will not occur.

Some students here may ask, since zend_object* has been saved directly in zval, why do we need EG (objects_store)?

There are two main reasons here:

1. We need to ensure that the destructors of all objects are called when the PHP request ends, because the object has a circular reference, then How to quickly traverse all living objects? EG (objects_store) is a very good choice.

2. When developing PHPNG, in order to ensure maximum backward compatibility, we still need to ensure that the interface for obtaining the handle of an object is obtained, and this handle must still maintain the original semantics.

But in reality, EG (objects_store) is no longer of much use, and we can remove it in the future.

Okay, another problem arises next. Let’s look at the definition of zend_object again and notice the properties_table[1] at the end. In other words, we will now allocate memory with the object’s properties together with the object. This is cache-friendly. But one change is that the zend_object structure may now become longer.

That brought me a problem when I was writing PHPNG. In the PHP5 era, many custom objects were defined like this (mysqli as an example):

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教程

The above is the detailed content of In-depth understanding of PHP7 kernel Object. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:laruence.com. If there is any infringement, please contact admin@php.cn delete