PHP数组实际占用内存大小原理解析


Posted in PHP onDecember 11, 2020

一般来说,PHP数组的内存利用率只有 1/10, 也就是说,一个在C语言里面100M 内存的数组,在PHP里面就要1G。下面我们可以粗略的估算PHP数组占用内存的大小,首先我们测试1000个元素的整数占用的内存:

<?php 
  echo memory_get_usage() , '<br>'; 
  $start = memory_get_usage(); 
  $a = Array(); 
  for ($i=0; $i<1000; $i++) { 
  $a[$i] = $i + $i; 
  } 
  $mid = memory_get_usage(); 
  echo memory_get_usage() , '<br>'; 
  for ($i=1000; $i<2000; $i++) { 
  $a[$i] = $i + $i; 
  } 
  $end = memory_get_usage(); 
  echo memory_get_usage() , '<br>'; 
  echo 'argv:', ($mid - $start)/1000 ,'bytes' , '<br>'; 
  echo 'argv:',($end - $mid)/1000 ,'bytes' , '<br>';

输出是:

353352
437848
522024
argv:84.416bytes
argv:84.176bytes

大概了解1000 个元素的整数数组需要占用 82k 内存,平均每个元素占用 84 个字节。而纯 C 中整体只需要 4k(一个整型占用4byte * 1000 )。memory_get_usage() 返回的结果并不是全是被数组占用了,还要包括一些 PHP 运行本身分配的一些结构,可能用内置函数生成的数组更接近真实的空间:

<?php
$start = memory_get_usage();
$a = array_fill(0, 10000, 1);
$mid = memory_get_usage(); //10k elements array;
echo 'argv:', ($mid - $start )/10000,'byte' , '<br>';
$b = array_fill(0, 10000, 1);
$end = memory_get_usage(); //10k elements array;
echo 'argv:', ($end - $mid)/10000 ,'byte' , '<br>';

得到:

argv:54.5792byte
argv:54.5784byte

从这个结果来看似乎一个数组元素大约占用了54个字节左右。

首先看一下32位机C语言各种类型占用的字节:

#include "stdafx.h" 
//#include <stdio.h> 
  
int main() { 
    printf("int:%d\nlong:%d\ndouble:%d\nchar*:%d\nsize_t:%d\n",  
    sizeof(int), sizeof(long),  
    sizeof(double), sizeof(char *),  
    sizeof(size_t)); 
  return  0;  
}

int:4
long:4
double:8
har*:4
size_t:4

在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内存,而10kx 10 的二维数组理论上只需要 6M 左右的空间,但是按照 memory_get_usage 的结果则两倍于此,[10k,5,2]的三维数组居然消耗了23M,小型数组果然是划不来的。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

PHP 相关文章推荐
PHP中在数据库中保存Checkbox数据(2)
Oct 09 PHP
无数据库的详细域名查询程序PHP版(4)
Oct 09 PHP
eWebEditor v3.8 商业完整版 (PHP)
Dec 06 PHP
PHP的可变变量名的使用方法分享
Feb 05 PHP
php 判断是否是中文/英文/数字示例代码
Sep 30 PHP
显示youtube视频缩略图和Vimeo视频缩略图代码分享
Feb 13 PHP
php时间函数用法分析
May 28 PHP
PHP 数组遍历foreach语法结构及实例
Jun 13 PHP
php版微信公众平台之微信网页登陆授权示例
Sep 23 PHP
基于PHP常用文件函数和目录函数整理
Aug 17 PHP
Laravel 默认邮箱登录改成用户名登录的实现方法
Aug 12 PHP
Laravel 自定命令以及生成文件的例子
Oct 23 PHP
PHP基于ip2long实现IP转换整形
Dec 11 #PHP
PHP哈希表实现算法原理解析
Dec 11 #PHP
PHP解决高并发的优化方案实例
Dec 10 #PHP
WordPress伪静态规则设置代码实例
Dec 10 #PHP
php远程请求CURL实例教程(爬虫、保存登录状态)
Dec 10 #PHP
php解析非标准json、非规范json的方式实例
Dec 10 #PHP
PHP连接MySQL数据库三种实现方法
Dec 10 #PHP
You might like
PHP explode()函数用法、切分字符串
2012/10/03 PHP
Win下如何安装PHP的APC拓展
2013/08/07 PHP
javascript 控制弹出窗口
2007/04/10 Javascript
基于jquery的从一个页面跳转到另一个页面的指定位置的实现代码(带平滑移动的效果)
2011/05/24 Javascript
用JQuery 判断某个属性是否存在hasAttr的解决方法
2013/04/26 Javascript
简单实用的全选反选按钮例子
2013/10/18 Javascript
javascript白色简洁计算器
2015/05/04 Javascript
JavaScript判断浏览器对CSS3属性是否支持的多种方法
2016/11/13 Javascript
angular4模块中给标签添加背景图的实现方法
2017/09/15 Javascript
vsCode安装使用教程和插件安装方法
2020/08/24 Javascript
Vue服务端渲染实践之Web应用首屏耗时最优化方案
2019/03/22 Javascript
微信小程序前端自定义分享的实现方法
2019/06/13 Javascript
[01:05:40]2014 DOTA2国际邀请赛中国区预选赛 5 23 CIS VS DT第三场
2014/05/24 DOTA
Python字符串的encode与decode研究心得乱码问题解决方法
2009/03/23 Python
详解Python中映射类型的内建函数和工厂函数
2015/08/19 Python
Python登录注册验证功能实现
2018/06/18 Python
解决Python获取字典dict中不存在的值时出错问题
2018/10/17 Python
python利用Opencv实现人脸识别功能
2019/04/25 Python
Python从函数参数类型引出元组实例分析
2019/05/28 Python
python如何保证输入键入数字的方法
2019/08/23 Python
Python 变量的创建过程详解
2019/09/02 Python
Python之变量类型和if判断方式
2020/05/05 Python
python中pyplot基础图标函数整理
2020/11/10 Python
20行代码教你用python给证件照换底色的方法示例
2021/02/05 Python
台湾线上百货零售购物平台:friDay购物
2017/08/18 全球购物
美国瑜伽服装和装备购物网站:Mukha Yoga
2019/02/22 全球购物
安德玛菲律宾官网:Under Armour菲律宾
2020/07/28 全球购物
2014单位领导班子四风对照检查材料思想汇报
2014/09/25 职场文书
关于工作经历的证明书
2014/10/11 职场文书
英语教师求职信范文
2015/03/20 职场文书
学校推普周活动总结
2015/05/07 职场文书
军事博物馆观后感
2015/06/05 职场文书
Django路由层如何获取正确的url
2021/07/15 Python
24年收藏2000多部退役军用电台
2022/02/18 无线电
《勇者辞职不干了》ED主题曲无字幕动画MV公开
2022/04/13 日漫
MySQL的表级锁,行级锁,排它锁和共享锁
2022/07/15 MySQL