实行步骤
扫描对代码进行词法和语法剖析,将内容切割成一个个片段 (token)
解析将代码片段筛掉空格注释等,将剩下的token 转成故意义的表达式
编译
将表达式编译成中间码 (opcode)
实行将中间码一条条实行
输出将实行结果输出到缓冲区
代码切割
$code = <<<EOF<?phpecho 'hello world'l;$data = 1+1;echo $data;EOF;print_r(token_get_all($code));
实行结果
Array( [0] => Array ( [0] => 376 [1] => <?php [2] => 1 ) [1] => Array ( [0] => 319 [1] => echo [2] => 2 ) [2] => Array ( [0] => 379 [1] => [2] => 2 ) [3] => Array ( [0] => 318 [1] => 'hello world' [2] => 2 ) [4] => Array ( [0] => 310 [1] => l [2] => 2 ) [5] => ; [6] => Array ( [0] => 379 [1] => [2] => 2 ) [7] => = [8] => Array ( [0] => 379 [1] => [2] => 3 ) [9] => Array ( [0] => 308 [1] => 1 [2] => 3 ) [10] => + [11] => Array ( [0] => 308 [1] => 1 [2] => 3 ) [12] => ; [13] => Array ( [0] => 379 [1] => [2] => 3 ) [14] => Array ( [0] => 319 [1] => echo [2] => 4 ) [15] => Array ( [0] => 379 [1] => [2] => 4 ) [16] => ;)
不雅观察上面可以得到三个信息
Token id 例如空格回车都是 379token 字符串行号Token id 是Zend内部token对应码, 定义于zend_language_parser.h
提高PHP实行效率
压缩代码,去除无用注释和空缺字符 (jquery.min.js)只管即便利用PHP内置函数或扩展函数用 apc/xcache/opcache 等缓存PHP的opcode缓存繁芜和耗时的运算结果能异步处理的不要同步处理,如发送邮件HHVM 为何速率快通过虚拟机(类似java) 直接将PHP转换成二进制字节码运行,实行时不用每次都去解析。
PHP底层变量数据构造利用 zval 构造体保存,下面代码在 Zend/zend.h 定义
typedef union _zvalue_value { / 下面定义描述了PHP的8大数据类型 / long lval; // 长整型 布尔型 double dval; // 浮点型 struct { // 字符串型 char val; int len; // strlen 返回这个值 } str; // NULL 类型表示本身为空 HashTable ht; // 数组利用哈希表实现 zend_object_value obj; // 工具类型 } zvalue_value;struct _zval_struct{ zvalue_value value; / 变量的值 / zend_uint refcount__gc; zend_uchar type; / 变量的类型 / zend_uchar is_ref__gc};typedef struct _zval_struct zval;
变量类型的定义,下面代码在 Zend/zend_types.h 定义
typedef unsigned int zend_uint;typedef unsigned char zend_uchar;
PHP数据8大类型统一通过 zvalue_value 联合体存储
联合体自身为空 描述 null long 描述 int bool double 描述 floatstr 描述 stringHashTable 描述 数字数组和关联数组zend_object_value 描述 工具和资源
PHP变量类型描述利用 zend_uchar type 描述
#define IS_NULL 0#define IS_LONG 1#define IS_DOUBLE 2#define IS_BOOL 3#define IS_ARRAY 4#define IS_OBJECT 5#define IS_STRING 6#define IS_RESOURCE 7#define IS_CONSTANT 8#define IS_CONSTANT_ARRAY 9
例如 $a=3 构造体如下(伪代码)
struct { zvalue_value = 3; refcount__gc = 1; type = IS_LONG; is_ref__gc = 0;}
$a 就像指针一样指向上面的构造体
PHP传值赋值中的COW特性在 _zval_struct 数据构造中还有下面两个成员
zend_uint refcount__gc 表示被引用多少次,每次引用+1zend_uchar is_ref__gc 表示普通变量还是引用变量下面通过编写代码理解引用机制
此处我利用的是 php5.4,须要安装 xdebug 来查看变量引用
把稳利用 php7.2 测试的时候引用数会一贯为0
安装 xdebug
编译天生 xdebug.so
yum -y install php-develtar xf xdebug-2.8.0alpha1.tgzcd xdebug-2.8.0alpha1phpizefind /usr/ -name \公众php-config\公众./configure --with-php-config=/usr/bin/php-configmake && make installls /usr/lib64/php/modules/
配置 xdebug
php --iniecho 'zend_extension=/usr/lib64/php/modules/xdebug.so' >> /etc/php.inisystemctl restart php72-php-fpm.servicephp -m | grep xdebug
编写测试代码
$a = 3;xdebug_debug_zval('a');
输出
a: (refcount=1, is_ref=0)=3
refcount 引用数为1is_ref 为0表示普通变量=3 表示值为3开始引用
$a = 3;$b = $a;xdebug_debug_zval('a');xdebug_debug_zval('b');
输出
a: (refcount=2, is_ref=0)=3
b: (refcount=2, is_ref=0)=3
授予新值
$a = 3;$b = $a;$b = 5;xdebug_debug_zval('a');xdebug_debug_zval('b');
输出
a: (refcount=1, is_ref=0)=3
b: (refcount=1, is_ref=0)=5
通报地址
$a = 3;$b = &$a;xdebug_debug_zval('a');xdebug_debug_zval('b');
输出
a: (refcount=2, is_ref=1)=3
b: (refcount=2, is_ref=1)=3
is_ref 该变量从普通变量转成引用变量
授予新值
$a = 3;$b = &$a;$c = $a;$b = 5;xdebug_debug_zval('a');xdebug_debug_zval('b');xdebug_debug_zval('c');
a: (refcount=2, is_ref=1)=5
b: (refcount=2, is_ref=1)=5
c: (refcount=1, is_ref=0)=3
总结
变量之间传值是通过引用赋值形式,无需开辟新的空间,节省资源当一个变量的值发生改变时,会复制一份来存新的值,取消引用,称为 copy on write (COW)引用变量不会触发COWPHP垃圾回收机制什么是垃圾
上海人: 你算什么垃圾?
如果一个zval 没有任何变量引用它,那它便是垃圾
?: (refcount=0, is_ref=0)=5
为啥要清理垃圾?
有人说php线程结束时会销毁所有变量,关闭所有句柄资源,不是自动的嘛,为啥要清理
如果php 短韶光内处理多个大文件时(如1G的电影),处理完不回收连续处理下一个,会造成内存溢出如果php 是个守护进程或者永劫光运行的脚本,不回收垃圾,逐步积累会造成内存溢出如何清理垃圾
找垃圾打消找垃圾通过 get_defined_vars 查看所有已定义变量
底层代码 zend_globals.h 定义了存储所有变量的两个哈希表
struct _zend_executor_globals { ... HashTable active_symbol_table; //局部变量符号表 HashTable symbol_table; //全局变量符号表 ...}
找到所有已定义的变量后,探求哪些变量引用数为0
struct _zval_struct{ ... zend_uint refcount__gc; zend_uchar is_ref__gc; ...}清理垃圾
如上面将 refcount__gc 为0的变量打消,这个思路是 PHP5.2版本之前的做法了
PHP5.3后用 引用计数系统中同步周期回收 算法来打消
实在新算法也是基于 refcount__gc 来回收,那么为什么要用新算法呢?
我们知道 refcount__gc 为0的一定是垃圾
但是并不是所有的垃圾 refcount__gc 都为0
也有 refcount__gc 不为0 的垃圾,如下实验可以产生不为0的垃圾
一个例子
$a = ['a'];$a[] = &$a; //引用自己xdebug_debug_zval('a');
输出
a: (refcount=2, is_ref=1)=array (
0 => (refcount=1, is_ref=0)='a',
1 => (refcount=2, is_ref=1)=...
)
第二元素: ... 代表递归,引用数2,是一个指针引用变量
官方供应的一张图
此时删掉 $a
$a = ['a'];$a[] = &$a;unset($a);xdebug_debug_zval('a');
输出
a: no such symbol
由于 $a 被删了,以是xdebug打印不出来,那么此时理论构造如下
(refcount=1, is_ref=1)=array (
0 => (refcount=1, is_ref=0)='a',
1 => (refcount=1, is_ref=1)=...
)
此时这个 zval 已经没有符号 (symbol) 引用了,但是它由于自己引用自己 refcount 为1,以是它是一个奇葩的垃圾
对付此情形php脚本结束时,会自动清理,当结束前会占用空间
因此 5.2 版本之前的垃圾清理思路不能覆盖这种情形
引用计数系统中同步周期回收算法 (Concurrent Cycle Collection in Reference Counted System)
连续以上面代码为例进行解释
新算法解释:
将 $a 作为疑似垃圾变量,进行仿照删除 (refcount--),然后仿照规复,规复条件是有其他变量引用该值时才进行仿照规复 (refcount++)
这样没能规复成功的便是垃圾了,把它删除即可。
例如上面的奇葩垃圾:
(refcount=1, is_ref=1)=array (
0 => (refcount=1, is_ref=0)='a',
1 => (refcount=1, is_ref=1)=...
)
仿照删除后变成:
(refcount=0, is_ref=1)=array (
0 => (refcount=0, is_ref=0)='a',
1 => (refcount=0, is_ref=1)=...
)
然后仿照规复:
由于没有类似 $a 这种 symbol 取指向该zval,以是规复不来
何时打消
通过上面的算法疑似垃圾会存放到一个区域(垃圾站),只有垃圾站满了才会急速打消。 把稳条件是开启垃圾回收
开启垃圾回收两种办法
php.ini 下的 zend.enable_gc = On 默认开启通过 gc_enable() 和 gc_disable() 来打开或关闭垃圾回收可以直策应用 gc_collect_cycles() 函数逼迫实行周期回收
末了说了那么多,实在只须要理解个中的事理,全体过程不须要PHP开拓职员参与,只须要调用 gc_enable() 或 gc_collect_cycles() 即可实现自动回收
PHP中数组底层剖析先复习一下数组特性
PHP 数组键的特性
$arr = [ 1 => 'a', '1' => 'b', 1.5 => 'c', true => 'd',];print_r($arr);
Array
(
[1] => d
)
key 可以是 integer 或 string
value 可以是任意类型
key 有如下特性
数字字符串会被转成整型 '1' => 1浮点型和布尔型转成整型 1.3 =》 1null会被当做空字符串 null => ''键名不可以利用工具和数组相同键名后面覆盖前面访问数组元素
$arr[key]$arr{key}5.4 版本后可以利用如下
function getArr(){ return [1,2,3,4]; }echo getArr()[2];
删除数组元素
$a = [1,2,3,4];foreach ($a as $k => $v) { unset($a[$k]);}$a[] = 5;print_r($a);
Array
(
[4] => 5
)
删除不会重置索引数组遍历
forforeacharray_walkarray_mapcurrent 和 next数组内部实现
实现利用两个构造 HashTable 和 bucket
什么是 HashTable
哈希表,通过关键字直接访问内存存储位置的数据构造。
通过把关键字进行哈希函数打算,得到映射到表中的位置使得: 查找,插入,修正,删除均在O(1)完成
下面代码在 Zend/zend_types.h
typedef struct _zend_array HashTable;struct _zend_array { zend_refcounted_h gc; union { struct { ZEND_ENDIAN_LOHI_4( zend_uchar flags, zend_uchar nApplyCount, zend_uchar nIteratorsCount, zend_uchar consistency) } v; uint32_t flags; } u; uint32_t nTableMask; Bucket arData; uint32_t nNumUsed; uint32_t nNumOfElements; uint32_t nTableSize; uint32_t nInternalPointer; zend_long nNextFreeElement; dtor_func_t pDestructor;};
旧版构造体
typedef struct _hashtable { uint nTableSize; uint nTableMask; uint nNumOfElements; ulong nNextFreeElement; Bucket pInternalPointer; Bucket pListHead; Bucket pListTail; Bucket arBuckets; unsigned char nApplyCount;};
成员解释nTableSizeBucket大小,最小为8,以2x增长nTableMask索引优化 nTableSize-1nNumOfElements元素个数 利用count()函数直接返回这个nNextFreeElement下一个索引位置 foreach利用pInternalPointer当前遍历的指针,foreach比for快的缘故原由,reset current函数利用pListHead存储数组头部指针pListTail存储数组尾部指针arBuckets实际存储容器arDataBucket数据nApplyCount记录被递归次数,防止去世循环递归
typedef struct bucket{ ulong h; uint nKeyLength; void pData; void pDataPtr; struct bucket pListNext; struct bucket pListLast; struct bucket pNext; struct bucket pLast; const char arKey;};
成员解释h对char key进行hash后的值,或是用户指天命字索引值nKeyLength哈希关键字长度,若为索引数字则为0pData指向value 一样平常是用户数据的副本,若为指针数据则指向指针pDataPtr如果是指针数据,指针会指向真正value,上面指向此pListNext全体hash表下个元素pListLast全体hash表上个元素pNext同一个hash的下一个元素pLast同一个hash的上一个元素arKey保存当前key对应的字符串
foreach 遍历先从 HashTable 的 pListHead -> pListNext
pNext 和 pLast 用于hash冲突同一个hash不同个bucket之间指针
PHP数组函数分类建议体验一下下面的函数,不用记住,只是留个印象,当你须要用的时候会遐想起来的,而不用自己去实现
遍历
prevnextcurrentendreseteach排序
sortrsortasortksortkrsortuasortuksort查找
in_arrayarray_searcharray_key_exists分合
array_slicearray_spliceimplodeexplodearray_combinearray_chunkarray_keysarray_valuesarray_columns凑集
array_mergearray_diffarray_diff_array_intersectarray_intersect_行列步队/栈
array_pusharray_poparray_shift其他
array_fillarray_fliparray_sumarray_reverse