Maison  >  Article  >  développement back-end  >  Compréhension approfondie de l'objet du noyau PHP7

Compréhension approfondie de l'objet du noyau PHP7

Guanhui
Guanhuiavant
2020-05-15 11:10:053727parcourir

Compréhension approfondie de l'objet du noyau PHP7

PHP5

Comme d'habitude, je vais d'abord vous amener à revoir zend_object en PHP5 (le contenu avant cette partie Il est également abordé dans l'article (vous pouvez l'ignorer si vous le connaissez). Si vous êtes intéressé auparavant, vous pouvez également lire les objets que j'ai écrits il y a 10 ans pour une compréhension approfondie des principes PHP

.

En PHP5, la définition des objets est la suivante :

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

où ce stocke la classe à laquelle appartient cet objet. Concernant propriétés_table et propriétés, propriétés_table est une propriété déclarée, et propriétés est une propriété dynamique, c'est-à-dire, par exemple :

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

Parce que dans la définition de Foo, nous déclarons public $a, alors $a est une propriété déclarée connue, et sa visibilité, y compris l'emplacement stocké dans la table_propriétés, est déterminée après déclaration.

Et $a->b est un attribut que nous ajoutons dynamiquement. Il n'appartient pas aux attributs déclarés. Il sera stocké dans les propriétés.

En fait, le type montre que Properties_table est un tableau de zval* et que Properties est une table de hachage.

Les gardes sont principalement utilisés pour la protection imbriquée lors de l'appel de méthodes magiques, telles que __isset/__get/__set.

En général, zend_object (ci-après dénommé objet) est en fait une existence relativement particulière en PHP5, seules la ressource et l'objet sont passés par référence, ce qui signifie que lors de l'affectation et du passage, c'est le transfert. Pour cette raison, en plus d'utiliser le comptage de références de Zval, Object et Resource utilisent également un système de comptage indépendant.

Nous pouvons également voir la différence entre l'objet et d'autres chaînes similaires de zval :

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

Pour les chaînes et les tableaux, zval stocke directement leurs pointeurs, tandis que pour l'objet est une structure 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;

Pour réellement obtenir l'objet, vous devez rechercher dans les compartiments d'objets globaux via ce zend_object_handle, qui est un index int :

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

Et EG( object_store).object_buckets est un tableau, enregistrant :

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;

Parmi eux, zend_object_store_bucket.bucket.obj.object enregistre le vrai pointeur zend_object, remarquez ceci. Il y a void * partout. peut également être enregistré ici.

De plus, nous avons également remarqué que zend_object_store_bueckt.bucket.obj.refcount, c'est le décompte de références de l'objet lui-même dont je viens de parler, c'est-à-dire que zval a son propre décompte de références, et l'objet a également son propre décompte de références.

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

De cette façon, l'objet peut garantir que le mécanisme COW est différent du zval ordinaire et peut garantir que l'objet peut être référencé globalement.

On peut voir qu'à partir d'un zval pour obtenir l'objet réel, nous devons d'abord obtenir le zval.value.obj.handle, puis utiliser cet index pour interroger EG (objects_store), ce qui est relativement inefficace .

Pour une autre opération courante, lors de l'obtention de la classe d'un objet zval, nous devons également appeler une fonction :

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

PHP7

Jusqu'à PHP7, comme je l'ai dit dans mon article précédent sur ZVAL du noyau PHP7, zval enregistre directement le pointeur de l'objet 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];
};

Et EG (objects_store) enregistre simplement un zend_object** et d'autres pointeurs :

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

Comme pour l'exemple COW précédent, pour IS_OBJECT, IS_TYPE_COPYABLE est utilisé pour le distinguer. Autrement dit, lorsqu'un COW se produit, si ce type ne définit pas IS_TYPE_COPYABLE, alors il n'y aura pas de "copie". .

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

Comme ci-dessus, vous pouvez voir que IS_TYPE_REFCOUNTED, IS_TYPE_COLLECTABLE et IS_TYPE_COPYABLE sont définis pour ARRAY, mais pour OBJECT, IS_TYPE_COPYABLE est manquant.

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

Si ce n'est pas Z_COPYABLE_P, alors la séparation au moment de l'écriture ne se produira pas.

Certains étudiants ici peuvent se demander, puisque zend_object* a été enregistré directement dans zval, pourquoi avons-nous besoin d'EG (objects_store) ?

Il y a deux raisons principales ici :

1. Nous devons nous assurer que les destructeurs de tous les objets sont appelés à la fin de la requête PHP, car l'objet a une référence circulaire, puis comment faire traverser rapidement tous les objets vivants ? EG (objects_store) est un très bon choix.

2. Lors du développement de PHPNG, afin d'assurer une compatibilité descendante maximale, nous devons toujours assurer l'interface pour obtenir le handle d'un objet, et ce handle doit toujours conserver la sémantique d'origine.

Mais en fait, EG (objects_store) ne sert plus à grand-chose, et on pourra s'en débarrasser à l'avenir.

D'accord, un autre problème se pose ensuite. Regardons à nouveau la définition de zend_object et notons le propriétés_table[1] à la fin, nous allons maintenant allouer de la mémoire avec les propriétés de l'objet avec l'objet. Ceci est respectueux du cache. Mais un changement est que la structure zend_object peut désormais devenir plus longue.

Cela m'a posé un problème lorsque j'écrivais PHPNG À l'ère PHP5, de nombreux objets personnalisés étaient définis comme ceci (mysqli par exemple) :

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer