Heim  >  Artikel  >  Backend-Entwicklung  >  Vertiefendes Verständnis des PHP7-Kernelobjekts

Vertiefendes Verständnis des PHP7-Kernelobjekts

Guanhui
Guanhuinach vorne
2020-05-15 11:10:053788Durchsuche

Vertiefendes Verständnis des PHP7-Kernelobjekts

PHP5

Wie üblich werde ich Sie zunächst dazu bringen, zend_object in PHP5 zu überprüfen (den Inhalt vor diesem Teil). Es wird auch in dem Artikel behandelt (Sie können ihn überspringen, wenn Sie schon einmal daran interessiert sind, können Sie auch die Objekte lesen, die ich vor 10 Jahren geschrieben habe, um ein tieferes Verständnis der PHP-Prinzipien zu erhalten).

In PHP5 lautet die Definition von Objekten wie folgt:

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

wobei ce die Klasse speichert, zu der dieses Objekt gehört. In Bezug auf Properties_table und Properties ist Properties_table eine deklarierte Eigenschaft und Properties eine dynamische Eigenschaft. das heißt zum Beispiel:

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

Da wir in der Definition von Foo öffentliches $a deklarieren, ist $a eine bekannte deklarierte Eigenschaft, und ihre Sichtbarkeit, einschließlich des in der Properties_table gespeicherten Speicherorts, wird danach bestimmt Erklärung.

Und $a->b ist ein Attribut, das wir dynamisch hinzufügen. Es gehört nicht zu den deklarierten Attributen. Dies wird in den Eigenschaften gespeichert.

Tatsächlich ist aus dem Typ ersichtlich, dass Properties_table ein Array von zval* und Properties eine Hashtable ist.

Guards werden hauptsächlich zum verschachtelten Schutz beim Aufruf magischer Methoden wie __isset/__get/__set verwendet.

Im Allgemeinen ist zend_object (im Folgenden als Objekt bezeichnet) in PHP5 tatsächlich eine relativ spezielle Existenz. In PHP5 werden nur Ressourcen und Objekte als Referenz übergeben, was bedeutet, dass es sich bei der Zuweisung und Übergabe um eine Übertragung handelt Aus diesem Grund verwenden Object und Resource zusätzlich zur Referenzzählung von Zval auch ein unabhängiges Zählsystem.

Wir können auch den Unterschied zwischen Objekt und anderen ähnlichen Zeichenfolgen von zval erkennen:

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

Für Zeichenfolgen und Arrays speichert zval ihre Zeiger direkt, während es für Objekt eine zend_object_value-Struktur gibt:

typedef unsigned int zend_object_handle;

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

Um das Objekt tatsächlich zu erhalten, müssen Sie in den globalen Objekt-Buckets über dieses zend_object_handle suchen, das ein int-Index ist:

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

Und EG( „objects_store).object_buckets ist ein Array, das Folgendes speichert:

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;

Unter anderem speichert zend_object_store_bucket.bucket.obj.object den echten zend_object-Zeiger. Beachten Sie Folgendes: Es gibt überall void *. Dies liegt daran, dass viele unserer erweiterten benutzerdefinierten Objekte können hier auch gespeichert werden.

Außerdem ist uns auch aufgefallen, dass zend_object_store_bueckt.bucket.obj.refcount der Referenzzähler des Objekts selbst ist, über das ich gerade gesprochen habe, das heißt, zval hat seinen eigenen Referenzzähler, und das Objekt hat auch einen eigenen Referenzzähler seinen eigenen Referenzzähler.

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

Auf diese Weise kann das Objekt sicherstellen, dass sich der COW-Mechanismus vom gewöhnlichen zval unterscheidet, und kann sicherstellen, dass auf das Objekt global verwiesen werden kann.

Es ist ersichtlich, dass wir von einem zval bis zum Abrufen des tatsächlichen Objekts zuerst das zval.value.obj.handle abrufen und dann diesen Index verwenden müssen, um EG (objects_store) abzufragen, was relativ ineffizient ist .

Für eine weitere häufige Operation müssen wir beim Abrufen der Klasse eines Zval-Objekts auch eine Funktion aufrufen:

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

PHP7

Bis zu PHP7, wie ich in meinem vorherigen Artikel über ZVAL des PHP7-Kernels sagte, speichert zval direkt den Zeiger des zend_object-Objekts:


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

Und EG (objects_store) speichert einfach ein zend_object** und andere Zeiger :

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

Wie im vorherigen COW-Beispiel wird für IS_OBJECT IS_TYPE_COPYABLE verwendet, um es zu unterscheiden. Das heißt, wenn ein COW auftritt und dieser Typ IS_TYPE_COPYABLE nicht festlegt, wird kein „Copy“ ausgeführt .

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

Wie oben können Sie sehen, dass IS_TYPE_REFCOUNTED, IS_TYPE_COLLECTABLE und IS_TYPE_COPYABLE für ARRAY definiert sind, aber für OBJECT fehlt IS_TYPE_COPYABLE.

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 es ist nicht Z_COPYABLE_P, dann erfolgt keine Schreibzeittrennung.

Einige Studenten hier fragen sich vielleicht, warum wir EG (objects_store) benötigen, da zend_object* direkt in zval gespeichert wurde?

Hier gibt es zwei Hauptgründe:

1 Wir müssen sicherstellen, dass die Destruktoren aller Objekte aufgerufen werden, wenn die PHP-Anfrage endet, da das Objekt einen Zirkelverweis hat schnell alle lebenden Objekte durchqueren? EG (objects_store) ist eine sehr gute Wahl.

2. Um maximale Abwärtskompatibilität zu gewährleisten, müssen wir bei der Entwicklung von PHPNG weiterhin die Schnittstelle zum Erhalten des Handles eines Objekts sicherstellen, und dieses Handle muss weiterhin die ursprüngliche Semantik beibehalten.

Aber tatsächlich ist EG (objects_store) nicht mehr von großem Nutzen und wir können es in Zukunft loswerden.

Okay, als nächstes sehen wir uns die Definition von zend_object noch einmal an und beachten die Properties_table[1] am Ende. Mit anderen Worten, wir werden jetzt zusammen mit dem Objekt Speicher mit den Eigenschaften zuweisen. Dies ist Cache-freundlich. Eine Änderung besteht jedoch darin, dass die zend_object-Struktur jetzt möglicherweise länger wird.

Das bereitete mir ein Problem, als ich PHPNG schrieb. In der PHP5-Ära wurden viele benutzerdefinierte Objekte wie folgt definiert (MySQL als Beispiel):

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

Das obige ist der detaillierte Inhalt vonVertiefendes Verständnis des PHP7-Kernelobjekts. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:laruence.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen