成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

深入理解PHP7之zval

Yuanf / 1259人閱讀

摘要:已經(jīng)發(fā)布如承諾我也要開(kāi)始這個(gè)系列的文章的編寫今天我想先和大家聊聊的變化在講變化的之前我們先來(lái)看看在下面是什么樣子回顧在的時(shí)候的定義如下對(duì)內(nèi)核有了解的同學(xué)應(yīng)該對(duì)這個(gè)結(jié)構(gòu)比較熟悉因?yàn)榭梢员硎疽磺兄械臄?shù)據(jù)類型所以它包含了一個(gè)字段表示這個(gè)存儲(chǔ)的是什

PHP7已經(jīng)發(fā)布, 如承諾, 我也要開(kāi)始這個(gè)系列的文章的編寫, 今天我想先和大家聊聊zval的變化. 在講zval變化的之前我們先來(lái)看看zval在PHP5下面是什么樣子

PHP5
zval回顧
在PHP5的時(shí)候, zval的定義如下:

struct _zval_struct {
    union {
        long lval;
        double dval;
        struct {
            char *val;
            int len;
        } str;
        HashTable *ht;
        zend_object_value obj;
        zend_ast *ast;
    } value;
    zend_uint refcount__gc;
    zend_uchar type;
    zend_uchar is_ref__gc;
};

對(duì)PHP5內(nèi)核有了解的同學(xué)應(yīng)該對(duì)這個(gè)結(jié)構(gòu)比較熟悉, 因?yàn)閦val可以表示一切PHP中的數(shù)據(jù)類型, 所以它包含了一個(gè)type字段, 表示這個(gè)zval存儲(chǔ)的是什么類型的值, 常見(jiàn)的可能選項(xiàng)是IS_NULL, IS_LONG, IS_STRING, IS_ARRAY, IS_OBJECT等等.

根據(jù)type字段的值不同, 我們就要用不同的方式解讀value的值, 這個(gè)value是個(gè)聯(lián)合體, 比如對(duì)于type是IS_STRING, 那么我們應(yīng)該用value.str來(lái)解讀zval.value字段, 而如果type是IS_LONG, 那么我們就要用value.lval來(lái)解讀.

另外, 我們知道PHP是用引用計(jì)數(shù)來(lái)做基本的垃圾回收的, 所以zval中有一個(gè)refcount__gc字段, 表示這個(gè)zval的引用數(shù)目, 但這里有一個(gè)要說(shuō)明的, 在5.3以前, 這個(gè)字段的名字還叫做refcount, 5.3以后, 在引入新的垃圾回收算法來(lái)對(duì)付循環(huán)引用計(jì)數(shù)的時(shí)候, 作者加入了大量的宏來(lái)操作refcount, 為了能讓錯(cuò)誤更快的顯現(xiàn), 所以改名為refcount__gc, 迫使大家都使用宏來(lái)操作refcount.

類似的, 還有is_ref, 這個(gè)值表示了PHP中的一個(gè)類型是否是引用, 這里我們可以看到是不是引用是一個(gè)標(biāo)志位.

這就是PHP5時(shí)代的zval, 在2013年我們做PHP5的opcache JIT的時(shí)候, 因?yàn)镴IT在實(shí)際項(xiàng)目中表現(xiàn)不佳, 我們轉(zhuǎn)而意識(shí)到這個(gè)結(jié)構(gòu)體的很多問(wèn)題. 而PHPNG項(xiàng)目就是從改寫這個(gè)結(jié)構(gòu)體而開(kāi)始的.

存在的問(wèn)題
PHP5的zval定義是隨著Zend Engine 2誕生的, 隨著時(shí)間的推移, 當(dāng)時(shí)設(shè)計(jì)的局限性也越來(lái)越明顯:

首先這個(gè)結(jié)構(gòu)體的大小是(在64位系統(tǒng))24個(gè)字節(jié), 我們仔細(xì)看這個(gè)zval.value聯(lián)合體, 其中zend_object_value是最大的長(zhǎng)板, 它導(dǎo)致整個(gè)value需要16個(gè)字節(jié), 這個(gè)應(yīng)該是很容易可以優(yōu)化掉的, 比如把它挪出來(lái), 用個(gè)指針代替,因?yàn)楫吘笽S_OBJECT也不是最最常用的類型.

第二, 這個(gè)結(jié)構(gòu)體的每一個(gè)字段都有明確的含義定義, 沒(méi)有預(yù)留任何的自定義字段, 導(dǎo)致在PHP5時(shí)代做很多的優(yōu)化的時(shí)候, 需要存儲(chǔ)一些和zval相關(guān)的信息的時(shí)候, 不得不采用其他結(jié)構(gòu)體映射, 或者外部包裝后打補(bǔ)丁的方式來(lái)擴(kuò)充zval, 比如5.3的時(shí)候新引入專門解決循環(huán)引用的GC, 它不得采用如下的比較hack的做法:

/* The following macroses override macroses from zend_alloc.h */
#undef  ALLOC_ZVAL
#define ALLOC_ZVAL(z)                                   
    do {                                                
        (z) = (zval*)emalloc(sizeof(zval_gc_info));     
        GC_ZVAL_INIT(z);                                
    } while (0)

它用zval_gc_info劫持了zval的分配:

typedef struct _zval_gc_info {
    zval z;
    union {
        gc_root_buffer       *buffered;
        struct _zval_gc_info *next;
    } u;
} zval_gc_info;

然后用zval_gc_info來(lái)擴(kuò)充了zval, 所以實(shí)際上來(lái)說(shuō)我們?cè)赑HP5時(shí)代申請(qǐng)一個(gè)zval其實(shí)真正的是分配了32個(gè)字節(jié), 但其實(shí)GC只需要關(guān)心IS_ARRAY和IS_OBJECT類型, 這樣就導(dǎo)致了大量的內(nèi)存浪費(fèi).

還比如我之前做的Taint擴(kuò)展, 我需要對(duì)于給一些字符串存儲(chǔ)一些標(biāo)記, zval里沒(méi)有任何地方可以使用, 所以我不得不采用非常手段:

Z_STRVAL_PP(ppzval) = erealloc(Z_STRVAL_PP(ppzval), Z_STRLEN_PP(ppzval) + 1 + PHP_TAINT_MAGIC_LENGTH);
PHP_TAINT_MARK(*ppzval, PHP_TAINT_MAGIC_POSSIBLE);

就是把字符串的長(zhǎng)度擴(kuò)充一個(gè)int, 然后用magic number做標(biāo)記寫到后面去, 這樣的做法安全性和穩(wěn)定性在技術(shù)上都是沒(méi)有保障的

第三, PHP的zval大部分都是按值傳遞, 寫時(shí)拷貝的值, 但是有倆個(gè)例外, 就是對(duì)象和資源, 他們永遠(yuǎn)都是按引用傳遞, 這樣就造成一個(gè)問(wèn)題, 對(duì)象和資源在除了zval中的引用計(jì)數(shù)以外, 還需要一個(gè)全局的引用計(jì)數(shù), 這樣才能保證內(nèi)存可以回收. 所以在PHP5的時(shí)代, 以對(duì)象為例, 它有倆套引用計(jì)數(shù), 一個(gè)是zval中的, 另外一個(gè)是obj自身的計(jì)數(shù):

typedef struct _zend_object_store_bucket {
    zend_bool destructor_called;
    zend_bool valid;
    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;

除了上面提到的兩套引用以外, 如果我們要獲取一個(gè)object, 則我們需要通過(guò)如下方式:

EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(z)].bucket.obj

經(jīng)過(guò)漫長(zhǎng)的多次內(nèi)存讀取, 才能獲取到真正的objec對(duì)象本身. 效率可想而知.

這一切都是因?yàn)閆end引擎最初設(shè)計(jì)的時(shí)候, 并沒(méi)有考慮到后來(lái)的對(duì)象. 一個(gè)良好的設(shè)計(jì), 一旦有了意外, 就會(huì)導(dǎo)致整個(gè)結(jié)構(gòu)變得復(fù)雜, 維護(hù)性降低, 這是一個(gè)很好的例子.

第四, 我們知道PHP中, 大量的計(jì)算都是面向字符串的, 然而因?yàn)橐糜?jì)數(shù)是作用在zval的, 那么就會(huì)導(dǎo)致如果要拷貝一個(gè)字符串類型的zval, 我們別無(wú)他法只能復(fù)制這個(gè)字符串. 當(dāng)我們把一個(gè)zval的字符串作為key添加到一個(gè)數(shù)組里的時(shí)候, 我們別無(wú)他法只能復(fù)制這個(gè)字符串. 雖然在PHP5.4的時(shí)候, 我們引入了INTERNED STRING, 但是還是不能根本解決這個(gè)問(wèn)題.

還比如, PHP中大量的結(jié)構(gòu)體都是基于Hashtable實(shí)現(xiàn)的, 增刪改查Hashtable的操作占據(jù)了大量的CPU時(shí)間, 而字符串要查找首先要求它的Hash值, 理論上我們完全可以把一個(gè)字符串的Hash值計(jì)算好以后, 就存下來(lái), 避免再次計(jì)算等等

第五, 這個(gè)是關(guān)于引用的, PHP5的時(shí)代, 我們采用寫時(shí)分離, 但是結(jié)合到引用這里就有了一個(gè)經(jīng)典的性能問(wèn)題:


當(dāng)我們調(diào)用dummy的時(shí)候, 本來(lái)只是簡(jiǎn)單的一個(gè)傳值就行的地方, 但是因?yàn)?array曾經(jīng)引用賦值給了$b, 所以導(dǎo)致$array變成了一個(gè)引用, 于是此處就會(huì)發(fā)生分離, 導(dǎo)致數(shù)組復(fù)制, 從而極大的拖慢性能, 這里有一個(gè)簡(jiǎn)單的測(cè)試:


我們?cè)?.6下運(yùn)行這個(gè)例子, 得到如下結(jié)果:

$ php-5.6/sapi/cli/php /tmp/1.php
Used 0.00045204162597656S
Used 4.2051479816437S

相差1萬(wàn)倍之多. 這就造成, 如果在一大段代碼中, 我不小心把一個(gè)變量變成了引用(比如foreach as &$v), 那么就有可能觸發(fā)到這個(gè)問(wèn)題, 造成嚴(yán)重的性能問(wèn)題, 然而卻又很難排查.

第六, 也是最重要的一個(gè), 為什么說(shuō)它重要呢? 因?yàn)檫@點(diǎn)促成了很大的性能提升, 我們習(xí)慣了在PHP5的時(shí)代調(diào)用MAKE_STD_ZVAL在堆內(nèi)存上分配一個(gè)zval, 然后對(duì)他進(jìn)行操作, 最后呢通過(guò)RETURN_ZVAL把這個(gè)zval的值"copy"給return_value, 然后又銷毀了這個(gè)zval, 比如pathinfo這個(gè)函數(shù):

PHP_FUNCTION(pathinfo)
{
.....
    MAKE_STD_ZVAL(tmp);
    array_init(tmp);
.....

    if (opt == PHP_PATHINFO_ALL) {
        RETURN_ZVAL(tmp, 0, 1);
    } else {
.....
}

這個(gè)tmp變量, 完全是一個(gè)臨時(shí)變量的作用, 我們又何必在堆內(nèi)存分配它呢? MAKE_STD_ZVAL/ALLOC_ZVAL在PHP5的時(shí)候, 到處都有, 是一個(gè)非常常見(jiàn)的用法, 如果我們能把這個(gè)變量用棧分配, 那無(wú)論是內(nèi)存分配, 還是緩存友好, 都是非常有利的

還有很多, 我就不一一詳細(xì)列舉了, 但是我相信你們也有了和我們當(dāng)時(shí)一樣的想法, zval必須得改改了, 對(duì)吧?

PHP7
現(xiàn)在的zval

到了PHP7中, zval變成了如下的結(jié)構(gòu), 要說(shuō)明的是, 這個(gè)是現(xiàn)在的結(jié)構(gòu), 已經(jīng)和PHPNG時(shí)候有了一些不同了, 因?yàn)槲覀冃略黾恿艘恍┙忉?(聯(lián)合體的字段), 但是總體大小, 結(jié)構(gòu), 是和PHPNG的時(shí)候一致的:

struct _zval_struct {
    union {
        zend_long         lval;             /* long value */
        double            dval;             /* double value */
        zend_refcounted  *counted;
        zend_string      *str;
        zend_array       *arr;
        zend_object      *obj;
        zend_resource    *res;
        zend_reference   *ref;
        zend_ast_ref     *ast;
        zval             *zv;
        void             *ptr;
        zend_class_entry *ce;
        zend_function    *func;
        struct {
            uint32_t w1;
            uint32_t w2;
        } ww;
    } value;
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                zend_uchar    type,         /* active type */
                zend_uchar    type_flags,
                zend_uchar    const_flags,
                zend_uchar    reserved)     /* call info for EX(This) */
        } v;
        uint32_t type_info;
    } u1;
    union {
        uint32_t     var_flags;
        uint32_t     next;                 /* hash collision chain */
        uint32_t     cache_slot;           /* literal cache slot */
        uint32_t     lineno;               /* line number (for ast nodes) */
        uint32_t     num_args;             /* arguments number for EX(This) */
        uint32_t     fe_pos;               /* foreach position */
        uint32_t     fe_iter_idx;          /* foreach iterator index */
    } u2;
};

雖然看起來(lái)變得好大, 但其實(shí)你仔細(xì)看, 全部都是聯(lián)合體, 這個(gè)新的zval在64位環(huán)境下,現(xiàn)在只需要16個(gè)字節(jié)(2個(gè)指針size), 它主要分為倆個(gè)部分, value和擴(kuò)充字段, 而擴(kuò)充字段又分為u1和u2倆個(gè)部分, 其中u1是type info, u2是各種輔助字段.

其中value部分, 是一個(gè)size_t大小(一個(gè)指針大小), 可以保存一個(gè)指針, 或者一個(gè)long, 或者一個(gè)double.

而type info部分則保存了這個(gè)zval的類型. 擴(kuò)充輔助字段則會(huì)在多個(gè)其他地方使用, 比如next, 就用在取代Hashtable中原來(lái)的拉鏈指針, 這部分會(huì)在以后介紹HashTable的時(shí)候再來(lái)詳解.

類型
PHP7中的zval的類型做了比較大的調(diào)整, 總體來(lái)說(shuō)有如下17種類型:

/* regular data types */
#define IS_UNDEF                    0
#define IS_NULL                     1
#define IS_FALSE                    2
#define IS_TRUE                     3
#define IS_LONG                     4
#define IS_DOUBLE                   5
#define IS_STRING                   6
#define IS_ARRAY                    7
#define IS_OBJECT                   8
#define IS_RESOURCE                 9
#define IS_REFERENCE                10

/* constant expressions */
#define IS_CONSTANT                 11
#define IS_CONSTANT_AST             12

/* fake types */
#define _IS_BOOL                    13
#define IS_CALLABLE                 14

/* internal types */
#define IS_INDIRECT                 15
#define IS_PTR                      17

其中PHP5的時(shí)候的IS_BOOL類型, 現(xiàn)在拆分成了IS_FALSE和IS_TRUE倆種類型. 而原來(lái)的引用是一個(gè)標(biāo)志位, 現(xiàn)在的引用是一種新的類型.

對(duì)于IS_INDIRECT和IS_PTR來(lái)說(shuō), 這倆個(gè)類型是用在內(nèi)部的保留類型, 用戶不會(huì)感知到, 這部分會(huì)在后續(xù)介紹HashTable的時(shí)候也一并介紹.

從PHP7開(kāi)始, 對(duì)于在zval的value字段中能保存下的值, 就不再對(duì)他們進(jìn)行引用計(jì)數(shù)了, 而是在拷貝的時(shí)候直接賦值, 這樣就省掉了大量的引用計(jì)數(shù)相關(guān)的操作, 這部分類型有:

IS_LONG
IS_DOUBLE

當(dāng)然對(duì)于那種根本沒(méi)有值, 只有類型的類型, 也不需要引用計(jì)數(shù)了:

IS_NULL
IS_FALSE
IS_TRUE

而對(duì)于復(fù)雜類型, 一個(gè)size_t保存不下的, 那么我們就用value來(lái)保存一個(gè)指針, 這個(gè)指針指向這個(gè)具體的值, 引用計(jì)數(shù)也隨之作用于這個(gè)值上, 而不在是作用于zval上了.

以IS_ARRAY為例:

struct _zend_array {
    zend_refcounted_h gc;
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                zend_uchar    flags,
                zend_uchar    nApplyCount,
                zend_uchar    nIteratorsCount,
                zend_uchar    reserve)
        } v;
        uint32_t flags;
    } u;
    uint32_t          nTableMask;
    Bucket           *arData;
    uint32_t          nNumUsed;
    uint32_t          nNumOfElements;
    uint32_t          nTableSize;
    uint32_t          nInternalPointer;
    zend_long         nNextFreeElement;
    dtor_func_t       pDestructor;
};

zval.value.arr將指向上面的這樣的一個(gè)結(jié)構(gòu)體, 由它實(shí)際保存一個(gè)數(shù)組, 引用計(jì)數(shù)部分保存在zend_refcounted_h結(jié)構(gòu)中:

typedef struct _zend_refcounted_h {
    uint32_t         refcount;          /* reference counter 32-bit */
    union {
        struct {
            ZEND_ENDIAN_LOHI_3(
                zend_uchar    type,
                zend_uchar    flags,    /* used for strings & objects */
                uint16_t      gc_info)  /* keeps GC root number (or 0) and color */
        } v;
        uint32_t type_info;
    } u;
} zend_refcounted_h;

所有的復(fù)雜類型的定義, 開(kāi)始的時(shí)候都是zend_refcounted_h結(jié)構(gòu), 這個(gè)結(jié)構(gòu)里除了引用計(jì)數(shù)以外, 還有GC相關(guān)的結(jié)構(gòu). 從而在做GC回收的時(shí)候, GC不需要關(guān)心具體類型是什么, 所有的它都可以當(dāng)做zend_refcounted*結(jié)構(gòu)來(lái)處理.

另外有一個(gè)需要說(shuō)明的就是大家可能會(huì)好奇的ZEND_ENDIAN_LOHI_4宏, 這個(gè)宏的作用是簡(jiǎn)化賦值, 它會(huì)保證在大端或者小端的機(jī)器上, 它定義的字段都按照一樣順序排列存儲(chǔ), 從而我們?cè)谫x值的時(shí)候, 不需要對(duì)它的字段分別賦值, 而是可以統(tǒng)一賦值, 比如對(duì)于上面的array結(jié)構(gòu)為例, 就可以通過(guò):

arr1.u.flags = arr2.u.flags;
一次完成相當(dāng)于如下的賦值序列:

arr1.u.v.flags                = arr2.u.v.flags;
arr1.u.v.nApplyCount         = arr2.u.v.nApplyCount;
arr1.u.v.nIteratorsCount    = arr2.u.v.nIteratorsCount;
arr1.u.v.reserve             = arr2.u.v.reserve;

還有一個(gè)大家可能會(huì)問(wèn)到的問(wèn)題是, 為什么不把type類型放到zval類型的前面, 因?yàn)槲覀冎喇?dāng)我們?nèi)ビ靡粋€(gè)zval的時(shí)候, 首先第一點(diǎn)肯定是先去獲取它的類型. 這里的一個(gè)原因是, 一個(gè)是倆者差別不大, 另外就是考慮到如果以后JIT的話, zval的類型如果能夠通過(guò)類型推導(dǎo)獲得, 就根本沒(méi)有必要去讀取它的type值了.

標(biāo)志位
除了數(shù)據(jù)類型以外, 以前的經(jīng)驗(yàn)也告訴我們, 一個(gè)數(shù)據(jù)除了它的類型以外, 還應(yīng)該有很多其他的屬性, 比如對(duì)于INTERNED STRING,它是一種在整個(gè)PHP請(qǐng)求期都存在的字符串(比如你寫在代碼中的字面量), 它不會(huì)被引用計(jì)數(shù)回收. 在5.4的版本中我們是通過(guò)預(yù)先申請(qǐng)一塊內(nèi)存, 然后再這個(gè)內(nèi)存中分配字符串, 最后用指針地址來(lái)比較, 如果一個(gè)字符串是屬于INTERNED STRING的內(nèi)存范圍內(nèi), 就認(rèn)為它是INTERNED STRING. 這樣做的缺點(diǎn)顯而易見(jiàn), 就是當(dāng)內(nèi)存不夠的時(shí)候, 我們就沒(méi)有辦法分配INTERNED STRING了, 另外也非常丑陋, 所以如果一個(gè)字符串能有一些屬性定義則這個(gè)實(shí)現(xiàn)就可以變得很優(yōu)雅.

還有, 比如現(xiàn)在我們對(duì)于IS_LONG, IS_TRUE等類型不再進(jìn)行引用計(jì)數(shù)了, 那么當(dāng)我們拿到一個(gè)zval的時(shí)候如何判斷它需要不需要引用計(jì)數(shù)呢? 想當(dāng)然的我們可能會(huì)說(shuō)用:

if (Z_TYPE_P(zv) >= IS_STRING) {
  //需要引用計(jì)數(shù)
}

但是你忘了, 還有INTERNED STRING的存在啊, 所以你也許要這么寫了:

if (Z_TYPE_P(zv) >= IS_STRING && !IS_INTERNED(Z_STR_P(zv))) {
  //需要引用計(jì)數(shù)
}

是不是已經(jīng)讓你感覺(jué)到有點(diǎn)不對(duì)勁了? 嗯,別急, 還有呢, 我們還在5.6的時(shí)候引入了常量數(shù)組, 這個(gè)數(shù)組呢會(huì)存儲(chǔ)在Opcache的共享內(nèi)存中, 它也不需要引用計(jì)數(shù):

if (Z_TYPE_P(zv) >= IS_STRING && !IS_INTERNED(Z_STR_P(zv))
    && (Z_TYPE_P(zv) != IS_ARRAY || !Z_IS_IMMUTABLE(Z_ARRVAL(zv)))) {
 //需要引用計(jì)數(shù)
}

你是不是也覺(jué)得這簡(jiǎn)直太丑陋了, 簡(jiǎn)直不能忍受這樣墨跡的代碼, 對(duì)吧?

是的,我們?cè)缦氲搅?,回頭看之前的zval定義, 注意到type_flags了么? 我們引入了一個(gè)標(biāo)志位, 叫做IS_TYPE_REFCOUNTED, 它會(huì)保存在zval.u1.v.type_flags中, 我們對(duì)于需要引用計(jì)數(shù)的類型就賦予這個(gè)標(biāo)志, 所以上面的判斷就可以變得很優(yōu)雅:

if (!(Z_TYPE_FLAGS(zv) & IS_TYPE_REFCOUNTED)) {
}

而對(duì)于INTERNED STRING來(lái)說(shuō), 這個(gè)IS_STR_INTERNED標(biāo)志位應(yīng)該是作用于字符串本身而不是zval的.

那么類似這樣的標(biāo)志位一共有多少呢?作用于zval的有:

IS_TYPE_CONSTANT            //是常量類型
IS_TYPE_IMMUTABLE           //不可變的類型, 比如存在共享內(nèi)存的數(shù)組
IS_TYPE_REFCOUNTED          //需要引用計(jì)數(shù)的類型
IS_TYPE_COLLECTABLE         //可能包含循環(huán)引用的類型(IS_ARRAY, IS_OBJECT)
IS_TYPE_COPYABLE            //可被復(fù)制的類型, 還記得我之前講的對(duì)象和資源的例外么? 對(duì)象

和資源就不是
IS_TYPE_SYMBOLTABLE //zval保存的是全局符號(hào)表, 這個(gè)在我之前做了一個(gè)調(diào)整以后沒(méi)用了, 但還保留著兼容,

                        //下個(gè)版本會(huì)去掉

作用于字符串的有:

IS_STR_PERSISTENT            //是malloc分配內(nèi)存的字符串
IS_STR_INTERNED             //INTERNED STRING
IS_STR_PERMANENT            //不可變的字符串, 用作哨兵作用
IS_STR_CONSTANT             //代表常量的字符串
IS_STR_CONSTANT_UNQUALIFIED //帶有可能命名空間的常量字符串

作用于數(shù)組的有:

#define IS_ARRAY_IMMUTABLE  //同IS_TYPE_IMMUTABLE

作用于對(duì)象的有:

IS_OBJ_APPLY_COUNT          //遞歸保護(hù)
IS_OBJ_DESTRUCTOR_CALLED    //析構(gòu)函數(shù)已經(jīng)調(diào)用
IS_OBJ_FREE_CALLED          //清理函數(shù)已經(jīng)調(diào)用
IS_OBJ_USE_GUARDS           //魔術(shù)方法遞歸保護(hù)
IS_OBJ_HAS_GUARDS           //是否有魔術(shù)方法遞歸保護(hù)標(biāo)志

有了這些預(yù)留的標(biāo)志位, 我們就會(huì)很方便的做一些以前不好做的事情, 就比如我自己的Taint擴(kuò)展, 現(xiàn)在把一個(gè)字符串標(biāo)記為污染的字符串就會(huì)變得無(wú)比簡(jiǎn)單:

/* it"s important that make sure
 * this value is not used by Zend or
 * any other extension agianst string */
#define IS_STR_TAINT_POSSIBLE    (1<<7)
#define TAINT_MARK(str)     (GC_FLAGS((str)) |= IS_STR_TAINT_POSSIBLE)

這個(gè)標(biāo)記就會(huì)一直隨著這個(gè)字符串的生存而存在的, 省掉了我之前的很多tricky的做法.

ZVAL預(yù)先分配
前面我們說(shuō)過(guò), PHP5的zval分配采用的是堆上分配內(nèi)存, 也就是在PHP預(yù)案代碼中隨處可見(jiàn)的MAKE_STD_ZVAL和ALLOC_ZVAL宏. 我們也知道了本來(lái)一個(gè)zval只需要24個(gè)字節(jié), 但是算上gc_info, 其實(shí)分配了32個(gè)字節(jié), 再加上PHP自己的內(nèi)存管理在分配內(nèi)存的時(shí)候都會(huì)在內(nèi)存前面保留一部分信息:

typedef struct _zend_mm_block {
    zend_mm_block_info info;
#if ZEND_DEBUG
    unsigned int magic;
# ifdef ZTS
    THREAD_T thread_id;
# endif
    zend_mm_debug_info debug;
#elif ZEND_MM_HEAP_PROTECTION
    zend_mm_debug_info debug;
#endif
} zend_mm_block;

從而導(dǎo)致實(shí)際上我們只需要24字節(jié)的內(nèi)存, 但最后竟然分配48個(gè)字節(jié)之多.

然而大部分的zval, 尤其是擴(kuò)展函數(shù)內(nèi)的zval, 我們想想它接受的參數(shù)來(lái)自外部的zval, 它把返回值返回給return_value, 這個(gè)也是來(lái)自外部的zval, 而中間變量的zval完全可以采用棧上分配. 也就是說(shuō)大部分的內(nèi)部函數(shù)都不需要在堆上分配內(nèi)存, 它需要的zval都可以來(lái)自外部.

于是當(dāng)時(shí)我們做了一個(gè)大膽的想法, 所有的zval都不需要多帶帶申請(qǐng).

而這個(gè)也很容易證明, PHP腳本中使用的zval, 要么存在于符號(hào)表, 要么就以臨時(shí)變量(IS_TMP_VAR)或者編譯變量(IS_CV)的形式存在. 前者存在于一個(gè)Hashtable中, 而在PHP7中Hashtable默認(rèn)保存的就是zval, 這部分的zval完全可以在Hashtable分配的時(shí)候一次性分配出來(lái), 后面的存在于execute_data之后, 數(shù)量也在編譯時(shí)刻確定好了, 也可以隨著execute_data一次性分配, 所以我們確實(shí)不再需要多帶帶在堆上申請(qǐng)zval了.

所以, 在PHP7開(kāi)始, 我們移除了MAKE_STD_ZVAL/ALLOC_ZVAL宏, 不再支持存堆內(nèi)存上申請(qǐng)zval. 函數(shù)內(nèi)部使用的zval要么來(lái)自外面輸入, 要么使用在棧上分配的臨時(shí)zval.

在后來(lái)的實(shí)踐中, 總結(jié)出來(lái)的可能對(duì)于開(kāi)發(fā)者來(lái)說(shuō)最大的變化就是, 之前的一些內(nèi)部函數(shù), 通過(guò)一些操作獲得一些信息, 然后分配一個(gè)zval, 返回給調(diào)用者的情況:

static zval * php_internal_function() {
    .....
    str = external_function();

    MAKE_STD_ZVAL(zv);

    ZVAL_STRING(zv, str, 0);

    return zv;
}
PHP_FUNCTION(test) {
    RETURN_ZVAL(php_internal_function(), 1, 1);
}

要么修改為, 這個(gè)zval由調(diào)用者傳遞:

static void php_internal_function(zval *zv) {
    .....
    str = external_function();

    ZVAL_STRING(zv, str);
    efree(str);
}

PHP_FUNCTION(test) {
    php_internal_function(return_value);
}

要么修改為, 這個(gè)函數(shù)返回原始素材:

static char * php_internal_function() {
    .....
    str = external_function();
    return str;
}

PHP_FUNCTION(test) {
    str = php_internal_function();
    RETURN_STRING(str);
    efree(str);
}

總結(jié)
(這塊還沒(méi)想好怎么說(shuō), 本來(lái)我是要引出Hashtable不再存在zval**, 從而引出引用類型的存在的必要性, 但是如果不先講Hashtable的結(jié)構(gòu), 這個(gè)引出貌似很突兀, 先這么著吧, 以后再來(lái)修改)

到現(xiàn)在我們基本上把zval的變化概況介紹完畢, 抽象的來(lái)說(shuō), 其實(shí)在PHP7中的zval, 已經(jīng)變成了一個(gè)值指針, 它要么保存著原始值, 要么保存著指向一個(gè)保存原始值的指針. 也就是說(shuō)現(xiàn)在的zval相當(dāng)于PHP5的時(shí)候的zval . 只不過(guò)相比于zval , 直接存儲(chǔ)zval, 我們可以省掉一次指針解引用, 從而提高緩存友好性.

其實(shí)PHP7的性能, 我們并沒(méi)有引入什么新的技術(shù)模式, 不過(guò)就是主要來(lái)自, 持續(xù)不懈的降低內(nèi)存占用, 提高緩存友好性, 降低執(zhí)行的指令數(shù)的這些原則而來(lái)的, 可以說(shuō)PHP7的重構(gòu)就是這三個(gè)原則.

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/29937.html

相關(guān)文章

  • 如何把擴(kuò)展從PHP5升級(jí)到PHP7

    摘要:所以,分配內(nèi)存的宏都被刪掉了。為的輪詢?cè)O(shè)計(jì)了一組宏,使用起來(lái)非常方便。 我在公司的生產(chǎn)環(huán)境已經(jīng)升級(jí)了PHP7,大部分活躍的擴(kuò)展都可以兼容PHP7或者有了PHP7的分支,但是處理protocolbuffers數(shù)據(jù)的擴(kuò)展一直沒(méi)有人來(lái)升級(jí),我只能摸著石頭過(guò)河,自己做一把升級(jí)了。目前已經(jīng)編譯通過(guò),處在處理單測(cè)失敗的case階段,歡迎大家一起討論。對(duì)PHP擴(kuò)展升級(jí)可以先看一下官方的升級(jí)說(shuō)明,ht...

    AWang 評(píng)論0 收藏0
  • [譯]變量在 PHP7 內(nèi)部的實(shí)現(xiàn)(二)

    摘要:第一部分講了和中關(guān)于變量最基礎(chǔ)的實(shí)現(xiàn)和變化。對(duì)象實(shí)際并不是直接嵌入到對(duì)象存儲(chǔ)的中的,因?yàn)閷?duì)象不是定長(zhǎng)的?,F(xiàn)在看看對(duì)象存儲(chǔ)中指針指向的實(shí)際的的結(jié)構(gòu),通常情況下用戶層面的對(duì)象定義如下指針指向的是對(duì)象實(shí)現(xiàn)的類原型。 本文第一部分和第二均翻譯自Nikita Popov(nikic,PHP 官方開(kāi)發(fā)組成員,柏林科技大學(xué)的學(xué)生) 的博客。為了更符合漢語(yǔ)的閱讀習(xí)慣,文中并不會(huì)逐字逐句的翻譯。 要理解...

    cpupro 評(píng)論0 收藏0
  • PHP7源碼分析】如何理解PHP虛擬機(jī)(一)

    摘要:操作數(shù)本身并無(wú)數(shù)據(jù)類型,它的數(shù)據(jù)類型由操作碼確定任何架構(gòu)的計(jì)算機(jī)都會(huì)對(duì)外提供指令集合運(yùn)算器通過(guò)執(zhí)行指令直接發(fā)出控制信號(hào)控制計(jì)算機(jī)各項(xiàng)操作。 順風(fēng)車運(yùn)營(yíng)研發(fā)團(tuán)隊(duì) 李樂(lè) 1.從物理機(jī)說(shuō)起 虛擬機(jī)也是計(jì)算機(jī),設(shè)計(jì)思想和物理機(jī)有很多相似之處; 1.1馮諾依曼體系結(jié)構(gòu) 馮·諾依曼是當(dāng)之無(wú)愧的數(shù)字計(jì)算機(jī)之父,當(dāng)前計(jì)算機(jī)都采用的是馮諾依曼體系結(jié)構(gòu);設(shè)計(jì)思想主要包含以下幾個(gè)方面: 指令和數(shù)據(jù)不加區(qū)別...

    tunny 評(píng)論0 收藏0
  • PHP7源碼分析】PHP7源碼研究淺談Zend虛擬機(jī)

    摘要:中詞法語(yǔ)法分析,生成抽象語(yǔ)法樹,然后編譯成及被執(zhí)行均由虛擬機(jī)完成。通常情況下這部分是可選部分,主要為便于程序的讀寫方便而使用。指令虛擬機(jī)的指令稱為,每條指令對(duì)應(yīng)一個(gè)。 作者 陳雷編程語(yǔ)言的虛擬機(jī)是一種可以運(yùn)行中間語(yǔ)言的程序。中間語(yǔ)言是抽象出的指令集,由原生語(yǔ)言編譯而成,作為虛擬機(jī)執(zhí)行階段的輸入。很多語(yǔ)言都實(shí)現(xiàn)了自己的虛擬機(jī),比如Java、C#和Lua。PHP語(yǔ)言也有自己的虛擬機(jī),稱為Z...

    馬龍駒 評(píng)論0 收藏0
  • php7內(nèi)核閱讀(1)--數(shù)據(jù)容器zval和zend_value

    摘要:本文主要是針對(duì),的話可以移步到慶哥的博客看,還有就是小菜我讀的是內(nèi)核剖析這本書。接下來(lái)我會(huì)使用到來(lái)調(diào)試源碼本文有參照博客中的部分內(nèi)容以及代碼。 前言 工作+實(shí)習(xí)快一年了,搞php后端開(kāi)發(fā),一直很迷茫怎么提高自己,就先從php源碼開(kāi)始吧,本人比較菜,本文章寫的比較趕時(shí)間,所以有什么錯(cuò)誤或者漏掉的地方,望各位大神指正,多交流才能成長(zhǎng)嘛,嘿嘿。本文主要是針對(duì)php7,php5的話可以移步到慶...

    canger 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<