ホームページ  >  記事  >  バックエンド開発  >  PHP7 カーネル オブジェクトの深い理解

PHP7 カーネル オブジェクトの深い理解

Guanhui
Guanhui転載
2020-05-15 11:10:053788ブラウズ

PHP7 カーネル オブジェクトの深い理解

PHP5

いつものように、最初に PHP5 の zend_object を確認します (この部分の前の内容)この記事でも取り上げています (詳しい方は読み飛ばしていただいても構いません)。以前に興味があった場合は、私が 10 年前に書いた PHP 原理を深く理解するためのオブジェクトも読むことができます。

PHP5 では、オブジェクトの定義は次のとおりです:

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

ここで、ce には、このオブジェクトが属するクラスが格納されます。properties_table とプロパティに関しては、properties_table は宣言されたプロパティ、properties は動的プロパティです。つまり、例:

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

Foo の定義で public $a を宣言すると、$a は既知の宣言されたプロパティとなり、properties_table に格納されている場所を含むその可視性は、その後に決定されるためです。宣言。

$a->b は動的に追加するプロパティです。宣言されたプロパティには属しません。これはプロパティに保存されます。

実際、型から、properties_table は zval* の配列であり、properties は Hashtable であることがわかります。

ガードは主に、__isset/__get/__set などのマジック メソッドを呼び出す際のネストされた保護に使用されます。

一般に、zend_object (以下、オブジェクト) は、実は PHP5 において比較的特殊な存在であり、PHP5 ではリソースとオブジェクトのみが参照渡しであり、代入して渡す際に、その転送が行われることになります。このため、Zval の参照カウントの使用に加えて、オブジェクトとリソースも独立したカウント システムを使用します。

object と zval からの他の同様の文字列の違いも確認できます:

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

文字列と配列の場合、zval はそれらのポインターを直接保存しますが、object の場合は 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;

実際にオブジェクトを取得するには、この zend_object_handle を通じてグローバル オブジェクト バケット内を検索する必要があります。これは 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;
}

And EG( object_store).object_buckets は配列であり、保存:

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;

その中で、zend_object_store_bucket.bucket.obj.object は実際の zend_object ポインターを保存します。どこにでも void * があることに注意してください。これは、拡張カスタム オブジェクトの多くが原因です。ここに保存することもできます。

さらに、zend_object_store_bueckt.bucket.obj.refcount にも気付きました。これは、先ほど説明したオブジェクト自体の参照カウントです。つまり、zval には独自の参照カウントがあり、object にも参照カウントがあります。独自の参照カウント。

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

このようにして、オブジェクトは COW メカニズムが通常の zval とは異なることを保証し、オブジェクトがグローバルに参照できることを保証できます。

zval から実際のオブジェクトを取得するには、まず zval.value.obj.handle を取得し、次にこのインデックスを使用して EG (objects_store) をクエリする必要があることがわかりますが、これは比較的非効率です。 。

もう 1 つの一般的な操作として、zval オブジェクトのクラスを取得するときに、次の関数を呼び出す必要があります。

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

そして、EG (objects_store) は単に 1 つの zend_object を保存します。 ** およびその他のポインタ:

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

前の COW の例では、IS_OBJECT の場合、それを区別するために IS_TYPE_COPYABLE が使用されます。つまり、COW が発生したときに、この型が IS_TYPE_COPYABLE を設定しないと、「コピー」は行われません。

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

上記のように、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 でない場合、書き込み時分離は発生しません。

ここにいる学生の中には、zend_object* が zval に直接保存されているのに、なぜ EG (objects_store) が必要なのかと疑問に思う人もいるかもしれません。

ここには主に 2 つの理由があります:

1. オブジェクトには循環参照があるため、PHP リクエストの終了時にすべてのオブジェクトのデストラクターが確実に呼び出されるようにする必要があります。すべての生きているオブジェクトを素早く横断しますか? EG (objects_store) は非常に良い選択です。

2. PHPNG を開発する場合、最大限の下位互換性を確保するために、オブジェクトのハンドルを取得するためのインターフェイスを確実に取得する必要があり、このハンドルは元のセマンティクスを維持する必要があります。

しかし実際には、EG (objects_store) はもうあまり役に立たないため、将来的には削除することができます。

さて、次に別の問題が発生します。zend_object の定義をもう一度見て、最後にあるproperties_table[1]に注目してください。言い換えれば、オブジェクトと一緒にオブジェクトのプロパティを使用してメモリを割り当てることになります。これはキャッシュに優しいです。ただし、1 つの変更点は、zend_object 構造体が長くなる可能性があることです。

PHPNG を書いているときに問題が発生しました。PHP5 時代には、多くのカスタム オブジェクトが次のように定義されていました (例として mysqli):

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

以上がPHP7 カーネル オブジェクトの深い理解の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はlaruence.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。