Maison  >  Article  >  développement back-end  >  Compréhension approfondie de zval dans le noyau PHP7

Compréhension approfondie de zval dans le noyau PHP7

藏色散人
藏色散人avant
2019-03-28 13:19:354332parcourir

PHP7 est sorti. Comme promis, je vais également commencer à écrire cette série d'articles. Je veux principalement faire comprendre à tout le monde à travers les articles ce que nous avons fait derrière l'énorme amélioration des performances de PHP7. à propos de zval d'abord. Avant de parler des changements dans zval, regardons d'abord à quoi ressemble zval sous PHP5

revue de zval

En PHP5, le La définition de zval est la suivante :

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

Les étudiants qui connaissent le noyau PHP5 devraient être familiers avec cette structure, car zval peut représenter tous les types de données en PHP, il contient donc un champ de type pour indiquer ce que ce zval stocke. Type de valeur, les options possibles courantes sont IS_NULL, IS_LONG, IS_STRING, IS_ARRAY, IS_OBJECT, etc.

En fonction de la valeur du champ de type, nous devons interpréter la valeur de la valeur de différentes manières. façons. Cette valeur est une union. Par exemple, si le type est IS_STRING, alors nous devons utiliser value.str pour interpréter le champ zval.value, et si le type est IS_LONG, alors nous devons utiliser value.lval pour l'interpréter.

De plus, nous savons que le comptage de références PHP est utilisé pour le garbage collection de base, il y a donc un champ refcount__gc dans zval, qui indique le nombre de références à ce zval. Mais il y a une chose à noter ici. , le nom de ce champ s'appelait encore refcount. Après 5.3, lors de l'introduction d'un nouvel algorithme de garbage collection pour gérer le comptage de références circulaires, l'auteur a ajouté un grand nombre de macros pour faire fonctionner refcount afin de faire apparaître les erreurs plus rapidement. renommé refcount__gc, obligeant tout le monde à utiliser des macros pour faire fonctionner refcount.

De même, il y a is_ref Cette valeur indique si un type en PHP est une référence. Ici, nous pouvons voir si la référence est un indicateur.

.

C'est le zval à l'ère PHP5. Lorsque nous travaillions sur l'opcache JIT pour PHP5 en 2013, parce que le JIT fonctionnait mal dans les projets réels, nous avons réalisé de nombreux problèmes avec cette structure. Le projet PHPNG a commencé par réécrire cette structure. .

Problèmes existants

La définition zval de PHP5 est née avec Zend Engine 2. Au fil du temps, les limites de la conception à cette époque sont devenues de plus en plus évidentes :

Tout d'abord, la taille de cette structure est (sur les systèmes 64 bits) de 24 octets. Examinons de plus près cette union zval.value qui est la plus grande carte longue, qui provoque la valeur entière. prendre 16 octets. Cela devrait être facile à optimiser, comme le déplacer et le remplacer par un pointeur, car après tout, IS_OBJECT n'est pas le type le plus couramment utilisé

Deuxièmement, tous les champs de cette structure. a La signification est clairement définie et aucun champ personnalisé n'est réservé. Par conséquent, lorsque vous effectuez de nombreuses optimisations à l'ère PHP5, lorsque vous devez stocker certaines informations liées à zval, vous devez utiliser un autre mappage de structure ou un packaging externe. et les correctifs. Pour étendre zval, par exemple, 5.3 a introduit un nouveau GC qui résout spécifiquement les références circulaires. Il ne doit pas utiliser l'approche de hacker suivante :

/* 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;

puis utiliser zval_gc_info pour étendre zval, donc en fait. Lorsque nous demandons un zval à l'ère PHP5, nous allouons en fait 32 octets, mais en fait le GC n'a besoin de se soucier que des types IS_ARRAY et IS_OBJECT, ce qui entraîne beaucoup de gaspillage de mémoire

Tout comme. ce que j'ai fait avant l'extension Taint, j'ai besoin de stocker des balises pour certaines chaînes, et il n'y a pas de place dans zval pour l'utiliser, donc je dois utiliser des moyens extraordinaires :

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

consiste à étendre la longueur de la chaîne par un int, puis utilisez un nombre magique pour la marquer et l'écrire plus tard. La sécurité et la stabilité de cette approche ne sont pas techniquement garanties

Troisièmement, la plupart des zvals de PHP sont passés par valeur et copiés lors de l'écriture. ., mais il y a deux exceptions, à savoir les objets et les ressources. Ils sont toujours passés par référence, ce qui pose un problème. En plus du nombre de références dans zval, les objets et les ressources ont également besoin d'un nombre de références global. assurez-vous que la mémoire peut être recyclée. Ainsi, à l'ère de PHP5, en prenant les objets comme exemple, il a deux ensembles de décomptes de références, l'un est en zval et l'autre est le décompte d'obj lui-même :

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;

En plus de ce qui précède En plus des deux ensembles de références, si nous voulons obtenir un objet, nous devons utiliser la méthode suivante :

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

Après un long processus de lectures multiples en mémoire, nous pouvons obtenir l'objet réel lui-même. L'efficacité peut être imaginée. Vous savez.

Tout cela est dû au fait que lorsque le moteur Zend a été conçu à l'origine, il n'a pas pris en compte les objets ultérieurs. Une bonne conception, une fois quelque chose. Des événements inattendus rendront la structure entière compliquée et la maintenabilité réduite, c'est un bon exemple.

Quatrièmement, nous savons qu'en PHP, un grand nombre de calculs sont orientés chaînes, mais parce que. le comptage de références est appliqué à zval, cela entraînera une copie. Pour un zval de type chaîne, nous n'avons pas d'autre choix que de copier la chaîne. Lorsque nous ajoutons une chaîne zval comme clé dans un tableau, nous n'avons pas d'autre choix que de copier la chaîne. string. Bien que dans PHP5.4, nous ayons introduit INTERNED STRING, cela ne peut toujours pas résoudre fondamentalement ce problème.

Par exemple, un grand nombre de structures en PHP sont implémentées sur la base de Hashtable, et les opérations d'ajout, la suppression, la modification et la vérification de Hashtable prennent beaucoup de temps CPU, et pour trouver une chaîne, vous avez d'abord besoin de sa valeur de hachage. Théoriquement, nous pouvons calculer la valeur de hachage d'une chaîne et la sauvegarder pour éviter un nouveau calcul. , etc.

第五, 这个是关于引用的, PHP5的时代, 我们采用写时分离, 但是结合到引用这里就有了一个经典的性能问题:

<?php
 
    function dummy($array) {}
 
    $array = range(1, 100000);
 
    $b = &$array;
 
    dummy($array);
?>

当我们调用dummy的时候, 本来只是简单的一个传值就行的地方, 但是因为$array曾经引用赋值给了$b, 所以导致$array变成了一个引用, 于是此处就会发生分离, 导致数组复制, 从而极大的拖慢性能, 这里有一个简单的测试:

<?php
$array = range(1, 100000);
 
function dummy($array) {}
 
$i = 0;
$start = microtime(true);
while($i++ < 100) {
    dummy($array);
}
 
printf("Used %sS\n", microtime(true) - $start);
 
$b = &$array; //注意这里, 假设我不小心把这个Array引用给了一个变量
$i = 0;
$start = microtime(true);
while($i++ < 100) {
    dummy($array);
}
printf("Used %ss\n", microtime(true) - $start);
?>

我们在5.6下运行这个例子, 得到如下结果:

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

相差1万倍之多. 这就造成, 如果在一大段代码中, 我不小心把一个变量变成了引用(比如foreach as &$v), 那么就有可能触发到这个问题, 造成严重的性能问题, 然而却又很难排查.

第六, 也是最重要的一个, 为什么说它重要呢? 因为这点促成了很大的性能提升, 我们习惯了在PHP5的时代调用MAKE_STD_ZVAL在堆内存上分配一个zval, 然后对他进行操作, 最后呢通过RETURN_ZVAL把这个zval的值”copy”给return_value, 然后又销毁了这个zval, 比如pathinfo这个函数:

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

这个tmp变量, 完全是一个临时变量的作用, 我们又何必在堆内存分配它呢? MAKE_STD_ZVAL/ALLOC_ZVAL在PHP5的时候, 到处都有, 是一个非常常见的用法, 如果我们能把这个变量用栈分配, 那无论是内存分配, 还是缓存友好, 都是非常有利的

还有很多, 我就不一一详细列举了, 但是我相信你们也有了和我们当时一样的想法, zval必须得改改了, 对吧?

现在的zval

到了PHP7中, zval变成了如下的结构, 要说明的是, 这个是现在的结构, 已经和PHPNG时候有了一些不同了, 因为我们新增加了一些解释 (联合体的字段), 但是总体大小, 结构, 是和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;
};

虽然看起来变得好大, 但其实你仔细看, 全部都是联合体, 这个新的zval在64位环境下,现在只需要16个字节(2个指针size), 它主要分为俩个部分, value和扩充字段, 而扩充字段又分为u1和u2俩个部分, 其中u1是type info, u2是各种辅助字段.

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

而type info部分则保存了这个zval的类型. 扩充辅助字段则会在多个其他地方使用, 比如next, 就用在取代Hashtable中原来的拉链指针, 这部分会在以后介绍HashTable的时候再来详解.

类型

PHP7中的zval的类型做了比较大的调整, 总体来说有如下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类型, 现在拆分成了IS_FALSE和IS_TRUE俩种类型. 而原来的引用是一个标志位, 现在的引用是一种新的类型.

对于IS_INDIRECT和IS_PTR来说, 这俩个类型是用在内部的保留类型, 用户不会感知到, 这部分会在后续介绍HashTable的时候也一并介绍.

从PHP7开始, 对于在zval的value字段中能保存下的值, 就不再对他们进行引用计数了, 而是在拷贝的时候直接赋值, 这样就省掉了大量的引用计数相关的操作, 这部分类型有:

IS_LONG
IS_DOUBLE

当然对于那种根本没有值, 只有类型的类型, 也不需要引用计数了:

IS_NULL
IS_FALSE
IS_TRUE

而对于复杂类型, 一个size_t保存不下的, 那么我们就用value来保存一个指针, 这个指针指向这个具体的值, 引用计数也随之作用于这个值上, 而不在是作用于zval上了.

Compréhension approfondie de zval dans le noyau PHP7

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将指向上面的这样的一个结构体, 由它实际保存一个数组, 引用计数部分保存在zend_refcounted_h结构中:

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;

所有的复杂类型的定义, 开始的时候都是zend_refcounted_h结构, 这个结构里除了引用计数以外, 还有GC相关的结构. 从而在做GC回收的时候, GC不需要关心具体类型是什么, 所有的它都可以当做zend_refcounted*结构来处理.

另外有一个需要说明的就是大家可能会好奇的ZEND_ENDIAN_LOHI_4宏, 这个宏的作用是简化赋值, 它会保证在大端或者小端的机器上, 它定义的字段都按照一样顺序排列存储, 从而我们在赋值的时候, 不需要对它的字段分别赋值, 而是可以统一赋值, 比如对于上面的array结构为例, 就可以通过:

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类型的前面, 因为我们知道当我们去用一个zval的时候, 首先第一点肯定是先去获取它的类型. 这里的一个原因是, 一个是俩者差别不大, 另外就是考虑到如果以后JIT的话, zval的类型如果能够通过类型推导获得, 就根本没有必要去读取它的type值了.

标志位

除了数据类型以外, 以前的经验也告诉我们, 一个数据除了它的类型以外, 还应该有很多其他的属性, 比如对于INTERNED STRING,它是一种在整个PHP请求期都存在的字符串(比如你写在代码中的字面量), 它不会被引用计数回收. 在5.4的版本中我们是通过预先申请一块内存, 然后再这个内存中分配字符串, 最后用指针地址来比较, 如果一个字符串是属于INTERNED STRING的内存范围内, 就认为它是INTERNED STRING. 这样做的缺点显而易见, 就是当内存不够的时候, 我们就没有办法分配INTERNED STRING了, 另外也非常丑陋, 所以如果一个字符串能有一些属性定义则这个实现就可以变得很优雅.

还有, 比如现在我们对于IS_LONG, IS_TRUE等类型不再进行引用计数了, 那么当我们拿到一个zval的时候如何判断它需要不需要引用计数呢? 想当然的我们可能会说用:

if (Z_TYPE_P(zv) >= IS_STRING) {
  //需要引用计数
}

但是你忘了, 还有INTERNED STRING的存在啊, 所以你也许要这么写了:

if (Z_TYPE_P(zv) >= IS_STRING && !IS_INTERNED(Z_STR_P(zv))) {
  //需要引用计数
}

是不是已经让你感觉到有点不对劲了? 嗯,别急, 还有呢, 我们还在5.6的时候引入了常量数组, 这个数组呢会存储在Opcache的共享内存中, 它也不需要引用计数:

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)))) {
 //需要引用计数
}

你是不是也觉得这简直太丑陋了, 简直不能忍受这样墨迹的代码, 对吧?

是的,我们早想到了,回头看之前的zval定义, 注意到type_flags了么? 我们引入了一个标志位, 叫做IS_TYPE_REFCOUNTED, 它会保存在zval.u1.v.type_flags中, 我们对于需要引用计数的类型就赋予这个标志, 所以上面的判断就可以变得很优雅:

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

而对于INTERNED STRING来说, 这个IS_STR_INTERNED标志位应该是作用于字符串本身而不是zval的.

那么类似这样的标志位一共有多少呢?作用于zval的有:

IS_TYPE_CONSTANT            //是常量类型
IS_TYPE_IMMUTABLE           //不可变的类型, 比如存在共享内存的数组
IS_TYPE_REFCOUNTED          //需要引用计数的类型
IS_TYPE_COLLECTABLE         //可能包含循环引用的类型(IS_ARRAY, IS_OBJECT)
IS_TYPE_COPYABLE            //可被复制的类型, 还记得我之前讲的对象和资源的例外么? 对象和资源就不是
IS_TYPE_SYMBOLTABLE         //zval保存的是全局符号表, 这个在我之前做了一个调整以后没用了, 但还保留着兼容,
                            //下个版本会去掉

作用于字符串的有:

IS_STR_PERSISTENT             //是malloc分配内存的字符串
IS_STR_INTERNED             //INTERNED STRING
IS_STR_PERMANENT            //不可变的字符串, 用作哨兵作用
IS_STR_CONSTANT             //代表常量的字符串
IS_STR_CONSTANT_UNQUALIFIED //带有可能命名空间的常量字符串

作用于数组的有:

#define IS_ARRAY_IMMUTABLE  //同IS_TYPE_IMMUTABLE

作用于对象的有:

IS_OBJ_APPLY_COUNT          //递归保护
IS_OBJ_DESTRUCTOR_CALLED    //析构函数已经调用
IS_OBJ_FREE_CALLED          //清理函数已经调用
IS_OBJ_USE_GUARDS           //魔术方法递归保护
IS_OBJ_HAS_GUARDS           //是否有魔术方法递归保护标志

有了这些预留的标志位, 我们就会很方便的做一些以前不好做的事情, 就比如我自己的Taint扩展, 现在把一个字符串标记为污染的字符串就会变得无比简单:

/* it&#39;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预先分配

前面我们说过, PHP5的zval分配采用的是堆上分配内存, 也就是在PHP预案代码中随处可见的MAKE_STD_ZVAL和ALLOC_ZVAL宏. 我们也知道了本来一个zval只需要24个字节, 但是算上gc_info, 其实分配了32个字节, 再加上PHP自己的内存管理在分配内存的时候都会在内存前面保留一部分信息:

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;

从而导致实际上我们只需要24字节的内存, 但最后竟然分配48个字节之多.

然而大部分的zval, 尤其是扩展函数内的zval, 我们想想它接受的参数来自外部的zval, 它把返回值返回给return_value, 这个也是来自外部的zval, 而中间变量的zval完全可以采用栈上分配. 也就是说大部分的内部函数都不需要在堆上分配内存, 它需要的zval都可以来自外部.

于是当时我们做了一个大胆的想法, 所有的zval都不需要单独申请.

而这个也很容易证明, PHP脚本中使用的zval, 要么存在于符号表, 要么就以临时变量(IS_TMP_VAR)或者编译变量(IS_CV)的形式存在. 前者存在于一个Hashtable中, 而在PHP7中Hashtable默认保存的就是zval, 这部分的zval完全可以在Hashtable分配的时候一次性分配出来, 后面的存在于execute_data之后, 数量也在编译时刻确定好了, 也可以随着execute_data一次性分配, 所以我们确实不再需要单独在堆上申请zval了.

所以, 在PHP7开始, 我们移除了MAKE_STD_ZVAL/ALLOC_ZVAL宏, 不再支持存堆内存上申请zval. 函数内部使用的zval要么来自外面输入, 要么使用在栈上分配的临时zval.

在后来的实践中, 总结出来的可能对于开发者来说最大的变化就是, 之前的一些内部函数, 通过一些操作获得一些信息, 然后分配一个zval, 返回给调用者的情况:

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由调用者传递:

static void php_internal_function(zval *zv) {
    .....
    str = external_function();
 
    ZVAL_STRING(zv, str);
     efree(str);
}
 
PHP_FUNCTION(test) {
     php_internal_function(return_value);
}

要么修改为, 这个函数返回原始素材:

static char * php_internal_function() {
    .....
    str = external_function();
     return str;
}
 
PHP_FUNCTION(test) {
     str = php_internal_function();
     RETURN_STRING(str);
     efree(str);
}

总结

(Je n'ai pas encore compris comment dire cela. À l'origine, je voulais introduire le fait que zval** n'existe plus dans Hashtable, introduisant ainsi la nécessité de l'existence de types référence. Cependant, si je ne le fais pas Je ne parle pas d'abord de la structure de Hashtable, cette introduction semble très abrupte, faisons cela pour l'instant, et modifions-le plus tard)

Jusqu'à présent, nous avons essentiellement introduit les changements dans zval. En fait, zval en PHP7 est devenu un pointeur de valeur. Soit la valeur d'origine est enregistrée, soit un pointeur pointant vers la valeur d'origine est enregistré. En d'autres termes, le zval actuel est équivalent au zval * en PHP5. , en stockant directement le zval, nous pouvons enregistrer un déréférencement, améliorant ainsi la convivialité du cache.

En fait, nous n'avons pas introduit de nouveaux modèles techniques pour les performances de PHP7, mais cela vient principalement d'une réduction continue. utilisation de la mémoire, amélioration de la convivialité du cache et réduction du temps d'exécution. On peut dire que la reconstruction de PHP7 est basée sur ces trois principes.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer