下面的做法会占用多大的内存?
list($appid,$openid) = ["testcontent","test"];
测试
$m0 = memory_get_usage();$k = range(1,200000);$m1 = memory_get_usage();echo round(($m1-$m0)/pow(1024,2),4) ."mb\n";foreach ($k as $i){ $n1 = "kk$i"; $n2 = "tt$i"; list($$n1,$$n2) = [$i,$i*3];}$m2 = memory_get_usage();echo round(($m2-$m1)/pow(1024,2),4) ."mb\n";$m1 = memory_get_usage();foreach ($k as $i){ $n1 = "kk$i"; $n2 = "tt$i"; $$n1 = $i+time(); $$n2 = 2*time();}$m2 = memory_get_usage();echo round(($m2-$m1)/pow(1024,2),4) ."mb\n";
上面运行输出的结果如下:
27.9404mb51.3041mb9.1553mb
可见数组占用的内存远大于正常分配的内容
原理
在php中都使用long类型来代表数字,没有使用int类型。大家都明白php是一种弱类型的语言,它不会去区分变量的类型,没有int float char *之类的概念。我们看看php在zend里面存储的变量,php中每个变量都有对应的 zval,zval结构体定义在zend/zend.h里面,其结构:
typedef struct _zval_struct zval; struct _zval_struct { /* variable information */ zvalue_value value; /* the value 1 12字节(32位机是12,64位机需要8+4+4=16) */ zend_uint refcount__gc; /* the number of references to this value (for gc) 4字节 */ zend_uchar type; /* the active type 1字节*/ zend_uchar is_ref__gc; /* whether this value is a reference (&) 1字节*/ };
php使用一种union结构来存储变量的值,即zvalue_value 是一个union,union变量所占用的内存是由最大成员数据空间决定。
typedef union _zvalue_value { long lval; /* long value */ double dval; /* double value */ struct { /* string value */ char *val; int len; } str; hashtable *ht; /* hash table value */ zend_object_value obj; /*object value */ } zvalue_value;
最大成员数据空间是struct str,指针占*val用4字节,int占用4字节,共8字节。
struct zval占用的空间为8+4+1+1 = 14字节,其实呢,在zval中数组,字符串和对象还需要另外的存储结构,数组则是一个 hashtable:
hashtable结构体定义在zend/zend_hash.h.
typedef struct _hashtable { uint ntablesize;//4 uint ntablemask;//4 uint nnumofelements;//4 ulong nnextfreeelement;//4 bucket *pinternalpointer; /* used for element traversal 4*/ bucket *plisthead;//4 bucket *plisttail;//4 bucket **arbuckets;//4 dtor_func_t pdestructor;//4 zend_bool persistent;//1 unsigned char napplycount;//1 zend_bool bapplyprotection;//1 #if zend_debug int inconsistent;//4 #endif } hashtable;
hashtable 结构需要 39 个字节,每个数组元素存储在 bucket 结构中:
typedef struct bucket { ulong h; /* used for numeric indexing 4字节 */ uint nkeylength; /* the length of the key (for string keys) 4字节 */ void *pdata; /* 4字节*/ void *pdataptr; /* 4字节*/ struct bucket *plistnext; /* php arrays are ordered. this gives the next element in that order4字节*/ struct bucket *plistlast; /* and this gives the previous element 4字节 */ struct bucket *pnext; /* the next element in this (doubly) linked list 4字节*/ struct bucket *plast; /* the previous element in this (doubly) linked list 4字节*/ char arkey[1]; /* must be last element 1字节*/ } bucket;
bucket 结构需要 33 个字节,键长超过四个字节的部分附加在 bucket 后面,而元素值很可能是一个 zval 结构,另外每个数组会分配一个由 arbuckets 指向的 bucket 指针数组, 虽然不能说每增加一个元素就需要一个指针,但是实际情况可能更糟。这么算来一个数组元素就会占用 54 个字节,与上面的估算几乎一样。
一个空数组至少会占用 14(zval) + 39(hashtable) + 33(arbuckets) = 86 个字节,作为一个变量应该在符号表中有个位置,也是一个数组元素,因此一个空数组变量需要 118 个字节来描述和存储。从空间的角度来看,小型数组平均代价较大,当然一个脚本中不会充斥数量很大的小型数组,可以以较小的空间代价来获取编程上的快捷。但如果将数组当作容器来使用就是另一番景象了,实际应用经常会遇到多维数组,而且元素居多。比如10k个元素的一维数组大概消耗540k内存,而10k x 10 的二维数组理论上只需要 6m 左右的空间,但是按照 memory_get_usage 的结果则两倍于此,[10k,5,2]的三维数组居然消耗了23m,小型数组果然是划不来的。
以上就是php 数组占用内存分析的详细内容。