PHP数组和值到底有多大

PHP数组和值到底有多大

内容导读

收集整理的这篇技术教程文章主要介绍了PHP数组和值到底有多大,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含8936字,纯文字阅读大概需要13分钟

内容图文

这篇文章是关于PHP 5的内存使用情况。对于本文所述的情况,PHP 7中的内存使用量大约低3倍。

在这篇文章中,我想以下面的脚本为例来研究PHP数组(以及一般的值)的内存使用情况,该脚本创建了100000个惟一的整数数组元素,并测量了结果的内存使用情况:

$startMemory = memory_get_usage();$array = range(1, 100000);echo memory_get_usage() - $startMemory, ' bytes';

你希望它是多少?简单来说,一个整数是8字节(在64位unix机器上使用long类型),您得到100,000个整数,因此显然需要800000字节。

现在尝试运行上面的代码。这就得到了14649024字节。是的,你没听错,是13.97 MB,比我们估计的多18倍。

那么,18的额外因数是怎么来的呢?

总结

对于那些不想知道整个故事的人,这里有一个涉及到的不同组件的内存使用的快速总结:














 |
64 bit
 | 32 bit---------------------------------------------------zval











 |
24 bytes | 16 bytes+ cyclic GC info





 |
 8 bytes |
4 bytes+ allocation header




|
16 bytes |
8 bytes===================================================zval (value) total




 |
48 bytes | 28 bytes===================================================bucket










 |
72 bytes | 36 bytes+ allocation header




|
16 bytes |
8 bytes+ pointer









|
 8 bytes |
4 bytes===================================================bucket (array element) total |
96 bytes | 48 bytes===================================================total total








| 144 bytes | 76 bytes

上述数字将根据您的操作系统、编译器和编译选项的不同而有所不同。例如,如果您使用调试或线程安全来编译PHP,您将得到不同的数字。但是我认为上面给出的大小是您将在Linux上的PHP 5.3的64位生产版本中看到的大小。

如果你用这144字节乘以100000个元素,你会得到14400000字节,也就是13.73 MB,这与实际数字非常接近——剩下的大部分都是未初始化bucket的指针,但是我将在后面讨论这个问题。

现在,如果您想对上面提到的值进行更详细的分析,请继续阅读:)

zvalue_value联盟

首先看看PHP是如何存储值的。正如您所知道的,PHP是一种弱类型语言,因此它需要某种方式在各种类型之间快速切换。PHP为此使用union,它在zend中定义如下。

typedef union _zvalue_value {

long lval;







// For integers and booleans

double dval;






// For floats (doubles)

struct {








// For strings



char *val;





//

 consisting of the string itself



int len;






//

 and its length

} str;

HashTable *ht;





// For arrays (hash tables)

zend_object_value obj;

// For objects} zvalue_value;

如果您不知道C,这不是一个问题,因为代码非常简单:union是一种使某些值可以作为各种类型访问的方法。例如,如果您执行zvalue_value->lval,您将得到一个被解释为整数的值。另一方面,如果您使用zvalue_value->ht,则该值将被解释为指向哈希表(即数组)的指针。

但我们不要在这里讲太多。对我们来说,唯一重要的是一个union的大小等于它的最大组件的大小。这里最大的组件是字符串结构体(zend_object_value结构体的大小与str结构体相同,但为了简单起见,我将省略它)。string struct存储一个指针(8字节)和一个整数(4字节),总共是12字节。由于内存对齐(12字节的结构并不酷,因为它们不是64位/ 8字节的倍数),结构的总大小将是16字节,这也是union作为一个整体的大小。

现在我们知道,由于PHP的动态类型,每个值不需要8字节,而是16字节。乘以100000个值得到1600000字节,也就是1.53 MB,但是实际的值是13.97 MB,所以我们还不能得到它。

zval的结构

这非常符合逻辑——union只存储值本身,但是PHP显然还需要存储类型和一些垃圾收集信息。保存此信息的结构称为zval,您可能已经听说过它。关于PHP为什么需要它的更多信息,我建议阅读Sara Golemon的一篇文章。无论如何,这个结构的定义如下:

struct _zval_struct {

zvalue_value value;

 // The value

zend_uint refcount__gc; // The number of references to this value (for GC)

zend_uchar type;



// The type

zend_uchar is_ref__gc;
// Whether this value is a reference (&)};

结构的大小由其组件的大小之和决定:zvalue_value为16字节(如上所计算),zend_uint为4字节,zend_uchars为1字节。总共是22字节。由于内存对齐,实际大小将是24字节。

因此,如果我们存储100,000个元素a 24字节,那么总共就是2400000,也就是2.29 MB,差距正在缩小,但是实际值仍然是原来的6倍多。

循环收集器(从PHP 5.3开始)

PHP 5.3引入了一个新的循环引用垃圾收集器。为此,PHP必须存储一些额外的数据。我不想在这里解释这个算法是如何工作的,你可以在手册的链接页上读到。对于我们的大小计算来说,重要的是PHP将把每个zval包装成zval_gc_info:

typedef struct _zval_gc_info {

zval z;

union {



gc_root_buffer


 *buffered;



struct _zval_gc_info *next;

} u;} zval_gc_info;

正如您所看到的,Zend只在它上面添加了一个union,它由两个指针组成。希望您还记得,union的大小就是它最大的组件的大小:两个union组件都是指针,因此它们的大小都是8字节。所以union的大小也是8字节。

如果我们把它加到24字节上面我们已经有32字节了。再乘以100000个元素,我们得到的内存使用量是3。05 MB。

Zend MM分配器

C与PHP不同,它不为您管理内存。你需要自己记录你的分配。为此,PHP使用了专门针对其需要优化的自定义内存管理器:Zend内存管理器。Zend MM基于Doug Lea的malloc,并添加了一些PHP特有的优化和特性(如内存限制、每次请求后清理等)。

这里对我们来说重要的是,MM为通过它完成的每个分配添加一个分配头。定义如下:

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;typedef struct _zend_mm_block_info {#if ZEND_MM_COOKIES

size_t _cookie;#endif

size_t _size; // size of the allocation

size_t _prev; // previous block (not sure what exactly this is)} zend_mm_block_info;

如您所见,这些定义充斥着大量的编译选项检查。如果你用堆保护,多线程,调试和MM cookie来构建PHP,那么如果你用堆保护,多线程,调试和MM cookie来构建PHP,那么如果你用堆保护,多线程,调试和MM cookie来构建PHP,那么分配头文件会更大。

对于本例,我们假设所有这些选项都是禁用的。在这种情况下,只剩下两个size_ts _size和_prev。size_t有8个字节(在64位上),所以分配头的总大小是16个字节——并且在每个分配上都添加了这个头。

现在我们需要再次调整zval大小。实际上,它不是32字节,而是48字节,这是由分配头决定的。乘以100000个元素是4。58 MB,实际值是13。97 MB,所以我们已经得到了大约三分之一的面积。

Buckets

到目前为止,我们只考虑单个值。但是PHP中的数组结构也会占用大量空间:“数组”在这里实际上是一个不合适的术语。PHP数组实际上是散列表/字典。那么哈希表是如何工作的呢?基本上,对于每个键,都会生成一个散列,该散列用作“real”C数组的偏移量。由于哈希值可能会冲突,具有相同哈希值的所有元素都存储在链表中。当访问一个元素时,PHP首先计算散列,查找正确的bucket并遍历链接列表,逐个元素比较确切的键。bucket的定义如下:

typedef struct bucket {

ulong h;








// The hash (or for int keys the key)

uint nKeyLength;




// The length of the key (for string keys)

void *pData;






// The actual data

void *pDataPtr;




 // ??? What's this ???

struct bucket *pListNext; // PHP arrays are ordered. This gives the next element in that order

struct bucket *pListLast; // and this gives the previous element

struct bucket *pNext;

 // The next element in this (doubly) linked list

struct bucket *pLast;

 // The previous element in this (doubly) linked list

const char *arKey;



// The key (for string keys)} Bucket;

正如您所看到的,需要存储大量数据才能获得PHP使用的抽象数组数据结构(PHP数组同时是数组、字典和链表,这当然需要大量信息)。单个组件的大小为无符号long为8字节,无符号int为4字节,指针为7乘以8字节。总共是68。添加对齐,得到72字节。

像zvals这样的bucket需要在头部分配,因此我们需要再次为分配头添加16个字节,从而得到88个字节。我们还需要在“real”C数组中存储指向这些Bucket的指针(Bucket ** arbucket;)我上面提到过,每个元素增加8个字节。所以总的来说,每个bucket需要96字节的存储空间。

如果每个值都需要一个bucket,那么bucket是96字节,zval是48字节,总共144字节。对于100000个元素,也就是14400000字节,即13.73 MB。

神秘的解决。

等等,还有0.24 MB !

最后的0.24 MB是由于未初始化的存储bucket造成的:理想情况下,存储bucket的实际C数组的大小应该与存储的数组元素的数量大致相同。通过这种方式,冲突最少(除非希望浪费大量内存)。但是PHP显然不能在每次添加元素时重新分配整个数组——这将非常缓慢。相反,如果内部bucket数组达到限制,PHP总是将其大小加倍。所以数组的大小总是2的幂。

在我们的例子中是2 ^ 17 = 131072。但是我们只需要100000个bucket,所以我们留下31072个bucket没有使用。这些bucket不会被分配(因此我们不需要花费全部的96字节),但是bucket指针(存储在内部桶数组中的那个)的内存仍然需要分配。所以我们另外使用8字节(一个指针)* 31072个元素。这是248576字节或0.23 MB,与丢失的内存匹配。(当然,这里仍然缺少一些字节,但是我不想在这里介绍。比如哈希表结构本身,变量等等)

神秘真的解决了。

这告诉我们什么?

PHP不是c,这就是所有这些告诉我们的。您不能期望像PHP这样的超级动态语言具有与C语言相同的高效内存使用。你不能。

但是,如果您确实想节省内存,可以考虑使用SplFixedArray处理大型静态数组。

看看这个修改后的脚本:

$startMemory = memory_get_usage();$array = new SplFixedArray(100000);for ($i = 0; $i < 100000; ++$i) {

$array[$i] = $i;}echo memory_get_usage() - $startMemory, ' bytes';

它基本上做的是相同的事情,但是如果运行它,您会注意到它只使用了“5600640字节”。这是每个元素56字节,因此比普通数组使用的每个元素144字节要少得多。这是因为一个固定的数组不需要bucket结构:所以它只需要每个元素一个zval(48字节)和一个指针(8字节),从而得到观察到的56字节。

以上就是PHP数组和值到底有多大的详细内容,更多请关注Gxl网其它相关文章!

内容总结

以上是为您收集整理的PHP数组和值到底有多大全部内容,希望文章能够帮你解决PHP数组和值到底有多大所遇到的程序开发问题。 如果觉得技术教程内容还不错,欢迎将网站推荐给程序员好友。

内容备注

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。


本文关键词:

联系我们

在线咨询:点击这里给我发消息

邮件:w420220301@qq.com