PHP数组内存利用率低和弱类型详细解读


Posted in PHP onAugust 10, 2017

这两天任务提前完成,可以喘口气沉淀一下,深入学习学习PHP。其实本来是想了解一下PHP性能优化相关的东西,但被网上的一句“PHP数组内存利用率低,C语言100MB的内存数组,PHP里需要1G”惊到了。PHP真的这么耗内存么?于是借此机会了解了PHP的数据类型实现方式。

先来做个测试:

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

所得结果:

    353352
    437848
    argv:84.416bytes

1000个元素的整数数组耗费内存(437848 - 353352)字节,约合82KB,也就是说每个元素所占内存84字节。在C语言中,一个int占位是4字节,整体相差了20倍。

但是网上又说memery_get_usage()返回的结果不全是数组占用,还包括PHP本身的一些结构,因此,换种方式,采用PHP内置函数生成数组试试:

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

 输出为:

argv:54.5792byte

比刚才略好,但也54字节,确实差了10倍左右。

究其原因,还得从PHP的底层实现说起。PHP是一种弱类型的语言,不分int,double,string之类的,统一一个'$'就能解决所有问题。PHP底层由C语言实现,每个变量都对应一个zval结构,其详细定义为:

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结构来存储变量的值,zval中zvalue_value类型的value变量即为一个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;

union类型占用内存的大小有其最大的成员所占的数据空间决定。在zvalue_value中,str结构体的int占4字节,char指针占4字节,故整个zvalue_value所占内存为8字节。

zval的大小即为8 + 4 + 1 + 1 = 14字节。

注意到zvalue_value中还有一个HashTable是做什么的?zval中,数组、字符串和对象还需要另外的存储结构,数组的存储结构即为HashTable。

HashTable定义给出:

typedef struct _hashtable { 
   uint nTableSize; //表长度,并非元素个数 
   uint nTableMask;//表的掩码,始终等于nTableSize-1 
   uint nNumOfElements;//存储的元素个数 
   ulong nNextFreeElement;//指向下一个空的元素位置 
   Bucket *pInternalPointer;//foreach循环时,用来记录当前遍历到的元素位置 
   Bucket *pListHead; 
   Bucket *pListTail; 
   Bucket **arBuckets;//存储的元素数组 
   dtor_func_t pDestructor;//析构函数 
   zend_bool persistent;//是否持久保存。从这可以发现,PHP数组是可以实现持久保存在内存中的,而无需每次请求都重新加载。 
   unsigned char nApplyCount; 
   zend_bool bApplyProtection; 
} HashTable;

除了几个记录table大小,所含元素数量的属性变量外,Bucket被多次使用到,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[1]; /* Must be last element */ 
} Bucket;

有点像一个链表,Bucket就像是一个链表节点,有具体的数据和指针,而HashTable就是一个array,保存着一串Bucket元素。PHP中多维数组的实现,不过就是Bucket里面存着另一个HashTable罢了。

算一算HashTable需要占用39个字节,Bucket需要33个字节。一个空的数组就需要占用14 + 39 + 33 = 86个字节。Bucket 结构需要 33 个字节,键长超过四个字节的部分附加在 Bucket 后面,而元素值很可能是一个 zval 结构,另外每个数组会分配一个由 arBuckets 指向的 Bucket 指针数组, 虽然不能说每增加一个元素就需要一个指针,但是实际情况可能更糟。这么算来一个数组元素就会占用 54 个字节,与上面的估算几乎一样。

从空间的角度来看,小型数组平均代价较大,当然一个脚本中不会充斥数量很大的小型数组,可以以较小的空间代价来获取编程上的快捷。但如果将数组当作容器来使用就是另一番景象了,实际应用经常会遇到多维数组,而且元素居多。比如10k个元素的一维数组大概消耗540k内存,而10k x 10 的二维数组理论上只需要 6M 左右的空间,但是按照 memory_get_usage 的结果则两倍于此,[10k,5,2]的三维数组居然消耗了23M,小型数组确实是划不来的。

PHP数组内存利用率低的原因,讲到这里,接下来的文章将解读PHP数组操作的具体实现。

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

PHP 相关文章推荐
PHP开发文件系统实例讲解
Oct 09 PHP
php str_pad 函数使用详解
Jan 13 PHP
php中判断数组是一维,二维,还是多维的解决方法
May 04 PHP
解析coreseek for sphinx的使用
Jun 21 PHP
php中将数组转成字符串并保存到数据库中的函数代码
Sep 29 PHP
php socket客户端及服务器端应用实例
Jul 04 PHP
php 参数过滤、数据过滤详解
Oct 26 PHP
PHP对象实例化单例方法
Jan 19 PHP
php实现解析xml并生成sql语句的方法
Feb 03 PHP
Laravel 实现密码重置功能
Feb 23 PHP
PHP实现基于3DES算法加密解密字符串示例
Aug 24 PHP
php设计模式之适配器模式实例分析【星际争霸游戏案例】
Apr 07 PHP
Laravel实现定时任务的示例代码
Aug 10 #PHP
PHP编程实现计算抽奖概率算法完整实例
Aug 09 #PHP
PHP实现将标点符号正则替换为空格的方法
Aug 09 #PHP
php实现的redis缓存类定义与使用方法示例
Aug 09 #PHP
PHP编程实现脚本异步执行的方法
Aug 09 #PHP
PHP并发查询MySQL的实例代码
Aug 09 #PHP
Yii2框架中使用PHPExcel导出Excel文件的示例
Aug 09 #PHP
You might like
PHP 字符串正则替换函数preg_replace使用说明
2011/07/15 PHP
ThinkPHP自定义函数解决模板标签加减运算的方法
2015/07/03 PHP
php文件上传的两种实现方法
2016/04/04 PHP
给moz-firefox下添加IE方法和属性
2007/04/10 Javascript
Js 冒泡事件阻止实现代码
2013/01/27 Javascript
Javascript原型链和原型的一个误区
2014/10/22 Javascript
javaScript中push函数用法实例分析
2015/06/08 Javascript
Bootstrap中CSS的使用方法
2016/02/17 Javascript
深入理解JavaScript中的call、apply、bind方法的区别
2016/05/30 Javascript
AngularJS验证信息框架的封装插件用法【w5cValidator扩展插件】
2016/11/03 Javascript
JS定时器用法分析【时钟与菜单中的应用】
2016/12/21 Javascript
从零学习node.js之搭建http服务器(二)
2017/02/21 Javascript
浅谈jQuery的bind和unbind事件(绑定和解绑事件)
2017/03/02 Javascript
利用express启动一个server服务的方法
2017/09/17 Javascript
Angular中的$watch方法详解
2017/09/18 Javascript
vue+node+webpack环境搭建教程
2017/11/05 Javascript
Bootstrap模态对话框中显示动态内容的方法
2018/08/10 Javascript
基于vue循环列表时点击跳转页面的方法
2018/08/31 Javascript
node.js之基础加密算法模块crypto详解
2018/09/11 Javascript
微信小程序实现基于三元运算验证手机号/姓名功能示例
2019/01/19 Javascript
js 计算月/周的第一天和最后一天代码
2020/02/01 Javascript
微信小程序云函数添加数据到数据库的方法
2020/03/04 Javascript
[01:03:18]DOTA2-DPC中国联赛 正赛 RNG vs Dynasty BO3 第一场 1月29日
2021/03/11 DOTA
Python实用日期时间处理方法汇总
2015/05/09 Python
详解Django rest_framework实现RESTful API
2018/05/24 Python
Python单元测试与测试用例简析
2019/11/09 Python
TensorFlow tf.nn.softmax_cross_entropy_with_logits的用法
2020/04/19 Python
keras 简单 lstm实例(基于one-hot编码)
2020/07/02 Python
李宁官方网店:中国运动品牌
2017/11/02 全球购物
我读书我快乐演讲稿
2014/05/07 职场文书
财务会计实训报告
2014/11/05 职场文书
推广普通话主题班会
2015/08/17 职场文书
Apache Calcite 实现方言转换的代码
2021/04/24 Servers
详解RedisTemplate下Redis分布式锁引发的系列问题
2021/04/27 Redis
frg-100简单操作(设置)说明
2022/04/05 无线电
鸿蒙3.0体验感怎么样? 鸿蒙3.0系统评测向
2022/08/14 数码科技