PHP內(nèi)核Object介紹

今天小編就為大家?guī)硪黄榻BPHP內(nèi)核Object的文章。小編覺得挺實(shí)用的,為此分享給大家做個(gè)參考。一起跟隨小編過來看看吧。

站在用戶的角度思考問題,與客戶深入溝通,找到嘉興網(wǎng)站設(shè)計(jì)與嘉興網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗(yàn),讓設(shè)計(jì)與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個(gè)性化、用戶體驗(yàn)好的作品,建站類型包括:成都網(wǎng)站建設(shè)、成都做網(wǎng)站、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣、空間域名、網(wǎng)站空間、企業(yè)郵箱。業(yè)務(wù)覆蓋嘉興地區(qū)。

PHP5中,對(duì)象的定義如下:

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

其中ce存儲(chǔ)了這個(gè)對(duì)象所屬的類, 關(guān)于properties_table和properties, properties_table是申明的屬性,properties是動(dòng)態(tài)屬性,也就是比如:

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

因?yàn)樵贔oo的定義中,我們申明了public $a, 那么$a就是已知的申明屬性,它的可見性,包括在properties_table中存儲(chǔ)的位置都是在申明后就確定的。

而$a->b, 是我們動(dòng)態(tài)給添加的屬性,它不屬于已經(jīng)申明的屬性,這個(gè)會(huì)存儲(chǔ)在properties中。

其實(shí)從類型上也能看出來, properties_table是zval*的數(shù)組,而properties是Hashtable。

guards主要用在魔術(shù)方法調(diào)用的時(shí)候嵌套保護(hù), 比如__isset/__get/__set。

總體來說, zend_object(以下簡(jiǎn)稱object)在PHP5中其實(shí)是一種相對(duì)特殊的存在, 在PHP5中,只有resource和object是引用傳遞,也就是說在賦值,傳遞的時(shí)候都是傳遞的本身,也正因?yàn)槿绱耍琌bject和Resource除了使用了Zval的引用計(jì)數(shù)以外,還采用了一套獨(dú)立自身的計(jì)數(shù)系統(tǒng)。

這個(gè)我們從zval中也能看出object和其他的類似字符串的的不同:

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

對(duì)于字符串和數(shù)組,zval中都直接保存它們的指針,而對(duì)于object卻是一個(gè)zend_object_value的結(jié)構(gòu)體:

typedef unsigned int zend_object_handle;

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

真正獲取對(duì)象是需要通過這個(gè)zend_object_handle,也就是一個(gè)int的索引去全局的object buckets中查找:

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

而EG(objects_store).object_buckets則是一個(gè)數(shù)組,保存著:

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 *, 這是因?yàn)槲覀兒芏鄶U(kuò)展的自定義對(duì)象,也是可以保存在這里的。

另外我們也注意到zend_object_store_bueckt.bucket.obj.refcount, 這個(gè)既是我剛剛講的object自身的引用計(jì)數(shù),也就是zval有一套自己的引用計(jì)數(shù),object也有一套引用計(jì)數(shù)。

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

這樣,可以讓object可以保證不同于普通的zval的COW機(jī)制,可以保證object可以全局傳引用。

可見,從一個(gè)zval到取到實(shí)際的object,我們需要首先獲取zval.value.obj.handle, 然后拿著這個(gè)索引再去EG(objects_store)查詢,效率比較低下。

對(duì)于另外一個(gè)常見的操作,就是獲取一個(gè)zval對(duì)象的類的時(shí)候,我們也需要需要調(diào)用一個(gè)函數(shù):

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

PHP7

到了PHP7, zval中直接保存了zend_object對(duì)象的指針:

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)也只是簡(jiǎn)單的保存了一個(gè)zend_object**等指針:

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

而對(duì)于前面的COW的例子,對(duì)于IS_OBJECT來說, 用IS_TYPE_COPYABLE來區(qū)分,也就是,當(dāng)發(fā)生COW的時(shí)候,如果這個(gè)類型沒有設(shè)置 IS_TYPE_COPYABLE,那么就不會(huì)發(fā)生"復(fù)制".

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

如上,大家可以看到對(duì)于ARRAY來說定義了IS_TYPE_REFCOUNTED, IS_TYPE_COLLECTABLE和IS_TYPE_COPYABLE, 但是對(duì)于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, 那么就不發(fā)生寫時(shí)分離。

這里有的同學(xué)會(huì)問,那既然已經(jīng)在zval中直接保存了zend_object*了,那為啥還需要EG(objects_store)呢?

這里有2個(gè)主要原因:

1. 我們需要在PHP請(qǐng)求結(jié)束的時(shí)候保證所有的對(duì)象的析構(gòu)函數(shù)都被調(diào)用,因?yàn)閛bject存在循環(huán)引用的情況,那如何快速的遍歷所有存活的對(duì)象呢? EG(objects_store)是一個(gè)很不錯(cuò)的選擇。

2. 在PHPNG開發(fā)的時(shí)候,為了保證最大向后兼容,我們還是需要保證獲取一個(gè)對(duì)象的handle的接口, 并且這個(gè)handle還是要保證原有的語義。

但實(shí)際上來說呢, 其實(shí)EG(objects_store)已經(jīng)沒啥太大的用處了, 我們是可以在將來去掉它的。

好,接下來出現(xiàn)了另外一個(gè)問題,我們?cè)倏纯磟end_object的定義, 注意到末尾的properties_table[1], 也就是說,我們現(xiàn)在會(huì)把object的屬性跟對(duì)象一起分配內(nèi)存。這樣做對(duì)緩存友好。 但帶來一個(gè)變化就是, zend_object這個(gè)結(jié)構(gòu)體現(xiàn)在是可能變長(zhǎng)的。

那在當(dāng)時(shí)寫PHPNG的時(shí)候就給我?guī)砹艘粋€(gè)問題, 在PHP5時(shí)代,很多的自定義對(duì)象是這么定義的(MySQLi為例):

typedef struct _mysqli_object {
    zend_object         zo;
    void                *ptr;
    HashTable           *prop_handler;
} mysqli_object; /* extends zend_object */

也就是說zend_object都在自定義的內(nèi)部類的頭部,這樣當(dāng)然有一個(gè)好處是可以很方便的做cast, 但是因?yàn)槟壳皕end_object變成變長(zhǎng)了,并且更嚴(yán)重的是你并不知道用戶在PHP繼承了你這個(gè)類以后,他新增了多少屬性的定義。

于是沒有辦法,在寫PHPNG的時(shí)候,我做了大量的調(diào)整如下(體力活):

typedef struct _mysqli_object {
    void                *ptr;
    HashTable           *prop_handler;
    zend_object         zo;
} mysqli_object; /* extends zend_object */

也就是把zend_object從頭部,挪到了尾部,那為了可以從zend_object取得自定義對(duì)象,我們需要新增定義:

static inline mysqli_object *php_mysqli_fetch_object(zend_object *obj) {
    return (mysqli_object *)((char*)(obj) - XtOffsetOf(mysqli_object, zo));
}

這樣類似的代碼大家應(yīng)該可以在很多使用了自定義對(duì)象的擴(kuò)展中看到。

這樣一來就規(guī)避了這個(gè)問題, 而在實(shí)際的分配自定義對(duì)象的時(shí)候,我們也需要采用如下的方法:

obj = ecalloc(1, sizeof(mysqli_object) + zend_object_properties_size(class_type));

這塊,大家在寫擴(kuò)展的時(shí)候,如果用到自定義的類,一定要注意。

而之前在PHP5中的guard, 我們也知道并不是所有的類都會(huì)申明魔術(shù)方法,在PHP5中把guard放在object中會(huì)在大部分情況下都是浪費(fèi)內(nèi)存, 所以在PHP7中會(huì),我們會(huì)根據(jù)一個(gè)類是否申明了魔術(shù)方法(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]);
....
}

從而可以在大部分情況下,節(jié)省一個(gè)指針的內(nèi)存分配。

PHP7中在取一個(gè)對(duì)象的類的時(shí)候,就會(huì)非常方便了, 直接zvalu.value.obj->ce即可,一些類所自定的handler也就可以很便捷的訪問到了, 性能提升明顯。

以上就是PHP內(nèi)核Object的詳細(xì)介紹內(nèi)容了,看完之后是否有所收獲呢?如果想了解更多相關(guān)內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊!

文章題目:PHP內(nèi)核Object介紹
路徑分享:http://muchs.cn/article48/ipjjep.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供建站公司、微信小程序、App開發(fā)、App設(shè)計(jì)、網(wǎng)站營銷、網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

h5響應(yīng)式網(wǎng)站建設(shè)