国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

深入理解PHP7之zval

Yuanf / 1232人閱讀

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

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

PHP5
zval回顧
在PHP5的時候, 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)該對這個結(jié)構(gòu)比較熟悉, 因為zval可以表示一切PHP中的數(shù)據(jù)類型, 所以它包含了一個type字段, 表示這個zval存儲的是什么類型的值, 常見的可能選項是IS_NULL, IS_LONG, IS_STRING, IS_ARRAY, IS_OBJECT等等.

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

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

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

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

存在的問題
PHP5的zval定義是隨著Zend Engine 2誕生的, 隨著時間的推移, 當時設(shè)計的局限性也越來越明顯:

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

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

還比如我之前做的Taint擴展, 我需要對于給一些字符串存儲一些標記, 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);

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

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

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

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

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

這一切都是因為Zend引擎最初設(shè)計的時候, 并沒有考慮到后來的對象. 一個良好的設(shè)計, 一旦有了意外, 就會導(dǎo)致整個結(jié)構(gòu)變得復(fù)雜, 維護性降低, 這是一個很好的例子.

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

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

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


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


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

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

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

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

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

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

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

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

PHP7
現(xiàn)在的zval

到了PHP7中, zval變成了如下的結(jié)構(gòu), 要說明的是, 這個是現(xiàn)在的結(jié)構(gòu), 已經(jīng)和PHPNG時候有了一些不同了, 因為我們新增加了一些解釋 (聯(lián)合體的字段), 但是總體大小, 結(jié)構(gòu), 是和PHPNG的時候一致的:

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

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

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

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

類型
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的時候的IS_BOOL類型, 現(xiàn)在拆分成了IS_FALSE和IS_TRUE倆種類型. 而原來的引用是一個標志位, 現(xiàn)在的引用是一種新的類型.

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

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

IS_LONG
IS_DOUBLE

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

IS_NULL
IS_FALSE
IS_TRUE

而對于復(fù)雜類型, 一個size_t保存不下的, 那么我們就用value來保存一個指針, 這個指針指向這個具體的值, 引用計數(shù)也隨之作用于這個值上, 而不在是作用于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將指向上面的這樣的一個結(jié)構(gòu)體, 由它實際保存一個數(shù)組, 引用計數(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ù)雜類型的定義, 開始的時候都是zend_refcounted_h結(jié)構(gòu), 這個結(jié)構(gòu)里除了引用計數(shù)以外, 還有GC相關(guān)的結(jié)構(gòu). 從而在做GC回收的時候, GC不需要關(guān)心具體類型是什么, 所有的它都可以當做zend_refcounted*結(jié)構(gòu)來處理.

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

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

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;

還有一個大家可能會問到的問題是, 為什么不把type類型放到zval類型的前面, 因為我們知道當我們?nèi)ビ靡粋€zval的時候, 首先第一點肯定是先去獲取它的類型. 這里的一個原因是, 一個是倆者差別不大, 另外就是考慮到如果以后JIT的話, zval的類型如果能夠通過類型推導(dǎo)獲得, 就根本沒有必要去讀取它的type值了.

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

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

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

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

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

是不是已經(jīng)讓你感覺到有點不對勁了? 嗯,別急, 還有呢, 我們還在5.6的時候引入了常量數(shù)組, 這個數(shù)組呢會存儲在Opcache的共享內(nèi)存中, 它也不需要引用計數(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)))) {
 //需要引用計數(shù)
}

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

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

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

而對于INTERNED STRING來說, 這個IS_STR_INTERNED標志位應(yīng)該是作用于字符串本身而不是zval的.

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

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

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

                        //下個版本會去掉

作用于字符串的有:

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          //遞歸保護
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ù)方法遞歸保護
IS_OBJ_HAS_GUARDS           //是否有魔術(shù)方法遞歸保護標志

有了這些預(yù)留的標志位, 我們就會很方便的做一些以前不好做的事情, 就比如我自己的Taint擴展, 現(xià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)

這個標記就會一直隨著這個字符串的生存而存在的, 省掉了我之前的很多tricky的做法.

ZVAL預(yù)先分配
前面我們說過, PHP5的zval分配采用的是堆上分配內(nèi)存, 也就是在PHP預(yù)案代碼中隨處可見的MAKE_STD_ZVAL和ALLOC_ZVAL宏. 我們也知道了本來一個zval只需要24個字節(jié), 但是算上gc_info, 其實分配了32個字節(jié), 再加上PHP自己的內(nèi)存管理在分配內(nèi)存的時候都會在內(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)致實際上我們只需要24字節(jié)的內(nèi)存, 但最后竟然分配48個字節(jié)之多.

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

于是當時我們做了一個大膽的想法, 所有的zval都不需要多帶帶申請.

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

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

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

要么修改為, 這個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);
}

要么修改為, 這個函數(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), 這個引出貌似很突兀, 先這么著吧, 以后再來修改)

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

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

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

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/29937.html

相關(guān)文章

  • 如何把擴展從PHP5升級到PHP7

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

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

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

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

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

    tunny 評論0 收藏0
  • PHP7源碼分析】PHP7源碼研究淺談Zend虛擬機

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

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

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

    canger 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<