真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

PHP7內(nèi)核之zval是什么

這篇文章給大家分享的是有關(guān)PHP7內(nèi)核之zval是什么的內(nèi)容。小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考。一起跟隨小編過來看看吧。

在輝南等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強(qiáng)發(fā)展的系統(tǒng)性、市場前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務(wù)理念,為客戶提供成都網(wǎng)站制作、網(wǎng)站設(shè)計(jì)、外貿(mào)網(wǎng)站建設(shè) 網(wǎng)站設(shè)計(jì)制作按需網(wǎng)站制作,公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),品牌網(wǎng)站建設(shè),營銷型網(wǎng)站,成都外貿(mào)網(wǎng)站制作,輝南網(wǎng)站建設(shè)費(fèi)用合理。

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

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

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

另外, 我們知道PHP是用引用計(jì)數(shù)來做基本的垃圾回收的, 所以zval中有一個(gè)refcount__gc字段, 表示這個(gè)zval的引用數(shù)目, 但這里有一個(gè)要說明的, 在5.3以前, 這個(gè)字段的名字還叫做refcount, 5.3以后, 在引入新的垃圾回收算法來對付循環(huán)引用計(jì)數(shù)的時(shí)候, 作者加入了大量的宏來操作refcount, 為了能讓錯(cuò)誤更快的顯現(xiàn), 所以改名為refcount__gc, 迫使大家都使用宏來操作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)體的很多問題. 而PHPNG項(xiàng)目就是從改寫這個(gè)結(jié)構(gòu)體而開始的.

存在的問題

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

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

第二, 這個(gè)結(jié)構(gòu)體的每一個(gè)字段都有明確的含義定義, 沒有預(yù)留任何的自定義字段, 導(dǎo)致在PHP5時(shí)代做很多的優(yōu)化的時(shí)候, 需要存儲(chǔ)一些和zval相關(guān)的信息的時(shí)候, 不得不采用其他結(jié)構(gòu)體映射, 或者外部包裝后打補(bǔ)丁的方式來擴(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來擴(kuò)充了zval, 所以實(shí)際上來說我們在PHP5時(shí)代申請一個(gè)zval其實(shí)真正的是分配了32個(gè)字節(jié), 但其實(shí)GC只需要關(guān)心IS_ARRAY和IS_OBJECT類型, 這樣就導(dǎo)致了大量的內(nèi)存浪費(fèi).

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

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

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

第三, PHP的zval大部分都是按值傳遞, 寫時(shí)拷貝的值, 但是有倆個(gè)例外, 就是對象和資源, 他們永遠(yuǎn)都是按引用傳遞, 這樣就造成一個(gè)問題, 對象和資源在除了zval中的引用計(jì)數(shù)以外, 還需要一個(gè)全局的引用計(jì)數(shù), 這樣才能保證內(nèi)存可以回收. 所以在PHP5的時(shí)代, 以對象為例, 它有倆套引用計(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, 則我們需要通過如下方式:

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

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

這一切都是因?yàn)閆end引擎最初設(shè)計(jì)的時(shí)候, 并沒有考慮到后來的對象. 一個(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, 我們別無他法只能復(fù)制這個(gè)字符串. 當(dāng)我們把一個(gè)zval的字符串作為key添加到一個(gè)數(shù)組里的時(shí)候, 我們別無他法只能復(fù)制這個(gè)字符串. 雖然在PHP5.4的時(shí)候, 我們引入了INTERNED STRING, 但是還是不能根本解決這個(gè)問題.

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

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

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

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

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

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

第六, 也是最重要的一個(gè), 為什么說它重要呢? 因?yàn)檫@點(diǎn)促成了很大的性能提升, 我們習(xí)慣了在PHP5的時(shí)代調(diào)用MAKE_STD_ZVAL在堆內(nèi)存上分配一個(gè)zval, 然后對他進(jìn)行操作, 最后呢通過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è)非常常見的用法, 如果我們能把這個(gè)變量用棧分配, 那無論是內(nèi)存分配, 還是緩存友好, 都是非常有利的

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

現(xiàn)在的zval

到了PHP7中, zval變成了如下的結(jié)構(gòu), 要說明的是, 這個(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;
};

雖然看起來變得好大, 但其實(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中原來的拉鏈指針, 這部分會(huì)在以后介紹HashTable的時(shí)候再來詳解.

類型

PHP7中的zval的類型做了比較大的調(diào)整, 總體來說有如下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倆種類型. 而原來的引用是一個(gè)標(biāo)志位, 現(xiàn)在的引用是一種新的類型.

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

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

IS_LONG
IS_DOUBLE

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

IS_NULL
IS_FALSE
IS_TRUE

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

PHP7內(nèi)核之zval是什么

PHP7 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ù)雜類型的定義, 開始的時(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)來處理.

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

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

標(biāo)志位

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

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

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)讓你感覺到有點(diǎn)不對勁了? 嗯,別急, 還有呢, 我們還在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ù)
}

你是不是也覺得這簡直太丑陋了, 簡直不能忍受這樣墨跡的代碼, 對吧?

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

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

而對于INTERNED STRING來說, 這個(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ù)制的類型, 還記得我之前講的對象和資源的例外么? 對象和資源就不是
IS_TYPE_SYMBOLTABLE         //zval保存的是全局符號(hào)表, 這個(gè)在我之前做了一個(gè)調(diào)整以后沒用了, 但還保留著兼容,
                            //下個(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

作用于對象的有:

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ì)變得無比簡單:

/* 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ù)先分配

前面我們說過, PHP5的zval分配采用的是堆上分配內(nèi)存, 也就是在PHP預(yù)案代碼中隨處可見的MAKE_STD_ZVAL和ALLOC_ZVAL宏. 我們也知道了本來一個(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ù)來自外部的zval, 它把返回值返回給return_value, 這個(gè)也是來自外部的zval, 而中間變量的zval完全可以采用棧上分配. 也就是說大部分的內(nèi)部函數(shù)都不需要在堆上分配內(nèi)存, 它需要的zval都可以來自外部.

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

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

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

在后來的實(shí)踐中, 總結(jié)出來的可能對于開發(fā)者來說最大的變化就是, 之前的一些內(nèi)部函數(shù), 通過一些操作獲得一些信息, 然后分配一個(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é)

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

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

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

感謝各位的閱讀!關(guān)于PHP7內(nèi)核之zval是什么就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!


網(wǎng)站欄目:PHP7內(nèi)核之zval是什么
網(wǎng)頁網(wǎng)址:http://weahome.cn/article/jgpgop.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部