对PHP内核的深刻理解(一)
作为一种简单而强大的语言,PHP可以提供许多适合Web的语言特性。本文从实践出发,遵循弱类型变量原理的探索,继续带领人们深入理解php内核。
最近在和网友交流的时候,被问到一个很奇怪的问题。也就是在一次操作中,添加一个引用后,发现性能慢了一万倍。在我看来,引用是一个非常容易出错的问题,尤其是在PHP中,那里有很多陷阱。因为我之前专门研究过这段PHP的源代码,所以能很清楚的分析出引用是怎么一回事。希望看完这篇文章,能彻底了解这个问题。如果你有任何问题或一些你想知道的问题,请给我留言。
我们先来看一段代码:
类RefferTest { private $ dataprivate $ testKeyfunction _ _ construct(){ $ key=' hello ';$this-data[$key]=范围(0,10000);$ this-TestKey=$ key;} function reffer($ key){ $ reffer=$ this-data[$ key];返回计数($ ref fer);} function no refer($ key){ return count($ this-data[$ key]);}函数测试(){ $ t1=micro time(true);for($ I=0;$ i 5000$ I){ $ this-ref fer($ this-TestKey);} $t2=微时间(真)-$ t1;var_dump('reffer: '。round($t2,4));$t1=微时间(真);for($ I=0;$ i 5000$ I){ $ this-no refer($ this-TestKey);} $t2=微时间(真)-$ t1;var _ dump(' norefffer : '。round($t2,4));} } $ test=new RefferTest();$ test-test();如果你写完这段代码,可以说reffer和noreffer的性能会差一万倍,那就没必要往下看了。本博客面向PHP初学者。您可以运行此代码并尝试它。比这糟糕一万倍。当然,网友遇到的问题的代码比上面的代码复杂,我特意简化了一下来说明问题。你可能在代码中看到了问题,但至于为什么。我觉得有必要分析一下。这样,以后在使用PHP的时候,就不会再犯同样的错误了。
为了减少复制,PHP采用了一种在writer上复制的机制。我觉得这是一个很常见的机制,你一定听说过。比如gcc的stl字符串的实现就采用了这样一种机制,字符串的赋值并不是真的复制,只有修改了才会复制。让我们从最简单的例子开始:
$a=str_repeat(',);$ b=$ a;$ a[]=' ';$a是一个非常大的字符串。如果$b=$a,复制会耗费大量内存和cpu,非常不经济。如果下面的代码没有修改$a和$b,则根本不需要复制。当然,$a是后来修改的。这个时候一定要抄,不然就不合逻辑了。但是,现在问题来了,怎么知道$a修改的时候一定要有这样的标记才能复制呢?方法是使用引用计数。引用计数也用于内存管理。
基本流程如下:
13360创建一个可以容纳10000个零的变量。
23360创建一个引用该变量的变量符号A。注意变量符号和变量不是一回事,它们是分开的。
如果从C语言的角度来看,PHP大概可以完成这样的事情:
char * varname=' asize _ t varname _ len=strlen(varname);Zend _ hash _ add(EG(active _ symbol _ table),varname,varname_len,var,sizeof(zval*),NULL);Active_symbol_table是PHP的一个符号表。所有可访问的变量都在这个表中。这是一个散列表。变量var包含10,000个零字符串。zval的结构如下:
typedef struct _ zval _ struct { zvalue _ value;zend _ uint refcountzend_uchar类型;zend_uchar是_ ref} zvaltypedef union _ zvalue _ value { long lval;双dvalstruct { char * valint len} strHashTable * htzend _ object _ value obj} zvalue _ valueZvalue_value是一个可以保存长整型、双精度型、字符串型、PHP数组和对象的并集。也就是所有的PHP类型。实际上,zval给zvalue_value增加了三个函数:type、reference is_ref和count refcount。这是PHP中常见的变量。如果你用PHP做一些比较大的事情,你会发现内存使用非常严重。也就是说,他的一个变量不是传统C语言的变量,它增加了很多东西。
好了,第一句话说完了。这是第二句话。第二句话很简单,它会产生一个新的变量符号B,并将其添加到active_symbol_table中,但它不会添加新的变量,而只是refcount。任务完成了。图片:
首先要注意的是,A和B只是一个符号,它们都是active_symbol_table表中的一个键,并且都有一个指向一个zval的指针,所以A和B在C语言中是完全一致的。我们得到了PHP变量的第一定律:
PHP变量第一定律:如果两个变量指向同一个zval,那么这两个变量是无法区分的。也就是说,a上的任何操作都是相对于B对称的.这里的对称性是这样理解的。是镜子里的你,不平等。例如,如果给A赋值,A将生成副本。同样,如果给B赋值,也会这样做,即B会生成一个副本。也就是说,a和b的行为方式是一样的。
第三,当writer发生时,PHP会判断refcount是否大于2。如果大于2,则复制zval,然后重新计数原始Zval-。这就是文案的全部意义。你一定觉得你对这一切都很熟悉,也很理解。
但是PHP不仅像writer上的复制一样简单,还存在引用问题。通过引入引用的概念,问题变得有些复杂。因为,引用这个标签意味着当你是一个作家时,你不需要复制它。这样,原始变量将被修改。从我们以前在学校学习的哲学来看,这是一对矛盾。它们是对立统一的,各有各的用处。所谓,存在即合理。
好,我们来看看这个矛盾。我们只考虑两种组合。许多组合是相似的。两种组合,即赋值前和引用后。
或者引用之前和赋值之后。我们将单独讨论它。我们先来看看:先有作业,后有参考的情况。
$ a=;$ b=$ a;$ c=$ a;$b=$a是对编写器行为的副本赋值。c和a是参考作业。让我们假设在上述情况下,我们可以使用一个zval,也就是说,我们不需要复制,那么情况如下:
根据我们PHP变量的第一定律,也就是说A、B、C的运算是对称的,但是非常明显的是,B的运算会产生复制行为,而A的运算不会产生复制,这和第一定律不同。也就是说,要使上述操作没有矛盾,必须进行分离。分离的原则是谁制造矛盾,谁就复制矛盾。显然第三句,$ c=$ a;制造矛盾。因此,复制内部变量的过程如下:
以上情况是作业第一,参考第二。在另一种情况下,引用在以下时间之前分配:
$ a=;$ b=$ a;$ c=$ a;根据PHP变量的第一定律,A、B、C必须分开,以保证定律的正确性。可以发现b和a明显是一群人,也就是说b和a的运算是对称的,可以指向同一个zval,而c的行为和a、b不同,所以改变c需要复制。看到这里,我想,如果你明白了,为什么一开始贴出来的代码中两个计数的差别这么大,你也应该明白。我和那个网友讨论的时候,最后说,既然这样,PHP设计的不好,我完全可以,$c不会先复制,c写好之后再复制。似乎还是很难理解一些东西。想想PHP的第一定律。你可以假设c指向同一个zval,没有分离,所以c的行为与a和b相同,is_ref=1,所以c不会复制。最后一个内部实现如下图所示:
我过去常常混淆这句话。现在,你可以用第一定律来分析所有情况。以后会写一些关于PHP内核分析的文章。如果你想了解更多关于PHP的一些方面,可以给我留言。
最后,这也是一个隐藏的错误。
函数count _ big array(){ global $ big array;返回计数($ big array);}这里没有显示引用,但是这里隐藏了一个引用。PHP将自动创建一个引用全局变量$bigarray的代码。如果在这里用count,效率会很慢。最好直接引用$GLOBAL数组。
下面的文章将介绍大家对php Kernel II的SAPI查询的深刻理解,希望大家继续关注。
版权声明:对PHP内核的深刻理解(一)是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。