php - PHP内存实际如何工作

标签 php arrays memory-management php-internals

我一直都在听和搜索新的php“好的写作习惯”,例如:检查数组键是否比在数组中搜索更好(以提高性能),但对于内存似乎也更好:

假设我们有:

$array = array
(
    'one'   => 1,
    'two'   => 2,
    'three' => 3,
    'four'  => 4,
);

这会分配1040字节的内存,


$array = array
(
    1 => 'one',
    2 => 'two',
    3 => 'three',
    4 => 'four',
);

需要1136字节

我知道keyvalue肯定会有不同的存储机制,但是
请您能实际给我指出该原理是如何工作的吗?

示例2(用于@teuneboon):
$array = array
(
    'one'   => '1',
    'two'   => '2',
    'three' => '3',
    'four'  => '4',
);

1168字节
$array = array
(
    '1' => 'one',
    '2' => 'two',
    '3' => 'three',
    '4' => 'four',
);

1136字节

消耗相同的内存:
  • 4 => 'four',
  • '4' => 'four',
  • 最佳答案

    注意,下面的答案适用于版本7之前的php ,因为PHP 7中引入了主要更改,其中还涉及值结构。

    TL; DR

    您的问题实际上不是关于“内存如何在PHP中工作”(在这里,我假设您的意思是“内存分配”),而是关于“数组如何在PHP中工作”-这两个问题是不同的。总结以下内容:

  • PHP数组不是经典意义上的“数组”。它们是哈希图
  • 用于PHP数组的哈希映射具有特定的结构,并使用许多其他存储内容,例如内部链接指针
  • PHP哈希映射的
  • 哈希映射项还使用其他字段来存储信息。而且-是的,不仅字符串/整数键很重要,而且字符串本身就是用于键的字符串本身也很重要。
  • 带有字符串键的
  • 选项在内存量方面将“获胜”,因为这两个选项都将被哈希化为ulong(无符号长)键哈希映射,因此真正的区别在于值,其中string-keys选项具有整数(固定长度)值,而整数键选项具有字符串(取决于字符的长度)值。但是由于可能发生的冲突,这种情况可能并不总是正确的。
  • “字符串数字”键(例如'4')将被视为整数键,并因为它是整数键而转换为整数哈希结果。因此,'4'=>'foo'4 => 'foo'是相同的东西。

  • 另外,重要的注意事项:此处的图形是PHP internals book的版权

    适用于PHP数组的哈希图

    PHP数组和C数组

    您应该意识到一件非常重要的事情:PHP是用C编写的,根本没有诸如“关联数组”之类的东西。因此,在C中,“数组”正是“数组”的含义-即,它只是内存中的连续区域,可以通过连续的偏移量对其进行访问。您的“键”只能是数字,整数,并且只能是连续的(从零开始)。例如,您不能在那里使用3-6'foo'作为您的“键”。

    因此,要实现PHP中的数组,有一个hash-map选项,它使用哈希函数对您的键进行哈希处理并将它们转换为整数,该整数可用于C数组。但是,该函数将永远无法在字符串键及其整数散列结果之间创建bijection。而且很容易理解为什么:因为字符串集的cardinality比整数集的基数大得多。让我们用示例进行说明:我们将重新计算所有长度不超过10的字符串,这些字符串仅包含字母数字符号(因此,0-9a-zA-Z,总共62个):总共可能有6210个字符串。大约是 8.39E + 17 。将它与 4E + 9 进行比较,我们对无符号整数(长整数,32位)类型有这种理解,您就会明白-将会发生冲突。

    PHP哈希映射键和冲突

    现在,要解决冲突,PHP会将具有相同哈希函数结果的项目放在一个链接列表中。因此,哈希映射将不只是“哈希元素列表”,而是存储指向元素列表的指针(某些列表中的每个元素都具有相同的哈希函数键)。这就是您将如何影响内存分配的地方:如果您的数组具有不会导致冲突的字符串键,则这些列表内不需要其他指针,因此将减少内存量(实际上,它是开销很小,但是,由于我们在谈论精确的内存分配,因此应该考虑到这一点)。而且,以同样的方式,如果您的字符串键将导致许多冲突,则将创建更多的额外指针,因此总存储量将更多。

    为了说明这些列表中的关系,下面是一个图形:

    enter image description here



    上面是PHP在应用哈希函数后如何解决冲突的方法。因此,您要解决的问题之一就是冲突解决列表中的指针。此外,链接列表的元素通常称为存储桶,而包含指向这些列表的开头的指针的数组在内部称为arBuckets。由于结构优化(因此,为了使元素删除等操作更快),真实列表元素具有两个指针,前一个元素和下一个元素-但这只会使非冲突/冲突数组的内存量差异变大,但不会改变概念本身。

    还有一个列表:订购

    要完全支持PHP中的数组,还需要维护顺序,因此可以使用另一个内部列表来实现。数组的每个元素也是该列表的成员。就内存分配而言,这不会有任何区别,因为应该在两个选项中都保留此列表,但是为了全面介绍,我要提到此列表。这是图形:

    enter image description here



    除了pListLastpListNext之外,还存储了指向订单列表开头和结尾的指针。同样,它与您的问题没有直接关系,但是我将进一步转储内部存储桶结构,其中存在这些指针。

    内部的数组元素

    现在我们准备开始研究:什么是数组元素,所以bucket:
    typedef struct bucket {
        ulong h;
        uint nKeyLength;
        void *pData;
        void *pDataPtr;
        struct bucket *pListNext;
        struct bucket *pListLast;
        struct bucket *pNext;
        struct bucket *pLast;
        char *arKey;
    } Bucket;
    

    我们来了:
  • h是key的整数(ulong)值,它是哈希函数的结果。对于整数键,它的格式为,与键本身的格式相同(哈希函数返回自身)
  • pNext/pLast是冲突解决链表
  • 中的指针
  • pListNext/pListLast是顺序分辨率链表
  • 中的指针
  • pData是指向存储值的指针。实际上,值与创建数组时插入的值不同,它是副本,但是为了避免不必要的开销,PHP使用了pDataPtr(so pData = &pDataPtr)

  • 从这个角度出发,您可能会发现下一个区别:字符串键将被散列(因此,h始终为ulong,因此大小相同),这将取决于存储在值中的内容。因此,对于您的string-keys数组,将有整数值,而对于integer-keys数组,将有字符串值,这有所作为。但是-不,这不是神奇的:您不能一直以这种方式存储字符串键来“保存内存”,因为如果您的键很大并且会有很多键,它将导致碰撞开销(嗯,很有可能,但是当然不能保证)。它仅对任意短字符串“起作用”,不会引起很多冲突。

    哈希表本身

    已经讨论过元素(存储桶)及其结构,但是哈希表本身也存在,实际上是数组数据结构。因此,它称为 _hashtable :
    typedef struct _hashtable {
        uint nTableSize;
        uint nTableMask;
        uint nNumOfElements;
        ulong nNextFreeElement;
        Bucket *pInternalPointer;   /* Used for element traversal */
        Bucket *pListHead;
        Bucket *pListTail;
        Bucket **arBuckets;
        dtor_func_t pDestructor;
        zend_bool persistent;
        unsigned char nApplyCount;
        zend_bool bApplyProtection;
    #if ZEND_DEBUG
        int inconsistent;
    #endif
    } HashTable;
    

    我不会描述所有字段,因为我已经提供了很多信息,而这仅与问题有关,但是我将简要描述此结构:
  • arBuckets是上面描述的内容,存储桶是
  • pListHead/pListTail是订单解决方案列表的指针
  • nTableSize确定哈希表的大小。这与内存分配直接相关:nTableSize始终为2的幂。因此,数组中有13或14个元素都没有关系:实际大小将为16。要估计数组大小时,请考虑到这一点。

  • 结论

    很难预测,在您的情况下,一个数组是否会比另一个数组大。是的,有一些遵循内部结构的准则,但是如果字符串键的长度与整数值(例如,示例中的'four''one')相当,那么实际的区别就在于-发生了多少次碰撞,发生了多少次碰撞分配了字节来保存值。

    但是选择合适的结构应该是有意义的,而不是内存。如果您打算建立相应的索引数据,那么选择总是显而易见的。上面的文章仅是一个目标:展示数组在PHP中的实际工作方式,以及在示例中您可以找到内存分配差异的地方。

    您还可以查看有关PHP中的数组和哈希表的文章:PHP内部人员手册中的Hash-tables in PHP:我从那里开始使用了一些图形。另外,要了解如何在PHP中分配值,请查看zval Structure文章,它可能会帮助您了解数组值的字符串和整数分配之间的区别。我在这里没有包括它的解释,因为对我来说更重要的一点是-显示数组数据结构以及在您的问题的字符串键/整数键的上下文中可能有什么不同。

    关于php - PHP内存实际如何工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25081344/

    相关文章:

    arrays - TypeScript 按数组中的对象对对象进行排序

    javascript - 如何定位名称等于变量值的数组?

    ios - 内存管理何时使用release

    c++ - 在这种情况下如何有效地使用 intrusive_ptr?

    java - PHP 运行 java jar 文件,参数作为 php 变量

    php - 用下划线替换 URL 中的空格

    php - 如何提高联系WebService的性能?

    python - 从 Numpy Meshgrid 生成位置向量

    php - 从价格中删除金额然后插入新价格

    c - 实际参数的指针不匹配?