Maison  >  Article  >  php教程  >  PHP 性能分析与实验:性能的微观分析(1)

PHP 性能分析与实验:性能的微观分析(1)

WBOY
WBOYoriginal
2016-06-13 08:54:28961parcourir

PHP 性能分析与实验:性能的微观分析(1)

在上一篇文章中,我们从 PHP 是解释性语言、动态语言和底层实现等三个方面,探讨了 PHP 性能的问题。本文就深入到 PHP 的微观层面,我们来了解 PHP 在使用和编写代码过程中,性能方面,可能需要注意和提升的地方。

在开始分析之前,我们得掌握一些与性能分析相关的函数。这些函数让我们对程序性能有更好的分析和评测。

一、性能分析相关的函数与命令

1.1、时间度量函数

平时我们常用 time() 函数,但是返回的是秒数,对于某段代码的内部性能分析,到秒的精度是不够的。于是要用 microtime 函数。而 microtime 函数可以返回两种形式,一是字符串的形式,一是浮点数的形式。不过需要注意的是,在缺省的情况下,返回的精度只有4位小数。为了获得更高的精确度,我们需要配置 precision。

如下是 microtime 的使用结果。

<ol class="dp-j"><li class="alt"><span><span>$start= microtime(</span><span class="keyword">true</span><span>); </span></span></li><li><span>echo $start.<span class="string">"/n"</span><span>; </span></span></li><li class="alt"><span>$end = microtime(<span class="keyword">true</span><span>); </span></span></li><li><span>echo $end.<span class="string">"/n"</span><span>; </span></span></li><li class="alt"><span>echo ($end-$start).<span class="string">"/n"</span><span>; </span></span></li></ol>

输出为:

<ol class="dp-j"><li class="alt"><span><span>bash-</span><span class="number">3.2</span><span># phptime.php </span></span></li><li><span> </span></li><li class="alt"><span><span class="number">1441360050.3286</span><span>  </span></span></li><li><span><span class="number">1441360050.3292</span><span>  </span></span></li><li class="alt"><span><span class="number">0.00053000450134277</span><span> </span></span></li></ol>

而在代码前面加上一行:

<ol class="dp-j"><li class="alt"><span><span>ini_set(</span><span class="string">"precision"</span><span>, </span><span class="number">16</span><span>); </span></span></li></ol>

输出为:

<ol class="dp-j"><li class="alt"><span><span>bash-</span><span class="number">3.2</span><span># phptime.php </span></span></li><li><span><span class="number">1441360210.932628</span><span>  </span></span></li><li class="alt"><span><span class="number">1441360210.932831</span><span>  </span></span></li><li><span><span class="number">0.0002031326293945312</span><span> </span></span></li></ol>

除了 microtime 内部统计之外, 还可以使用 getrusage 来取得用户态的时长。在实际的操作中,也常用 time 命令来计算整个程序的运行时长,通过多次运行或者修改代码后运行,得到不同的时间长度以得到效率上的区别。 具体用法是:time phptime.php ,则在程序运行完成之后,不管是否正常结束退出,都会有相关的统计。

<ol class="dp-j"><li class="alt"><span><span>bash-</span><span class="number">3.2</span><span># time phptime.php </span></span></li><li><span><span class="number">1441360373.150756</span><span>  </span></span></li><li class="alt"><span><span class="number">1441360373.150959</span><span>  </span></span></li><li><span><span class="number">0.0002031326293945312</span><span> </span></span></li><li class="alt"><span>real 0m0.186s  </span></li><li><span>user 0m0.072s  </span></li><li class="alt"><span>sys 0m0.077s </span></li></ol>

因为本文所讨论的性能问题,往往分析上百万次调用之后的差距与趋势,为了避免代码中存在一些时间统计代码,后面我们使用 time 命令居多。

1.2、内存使用相关函数

分析内存使用的函数有两个:memory_ get_ usage、memory_ get_ peak_usage,前者可以获得程序在调用的时间点,即当前所使用的内存,后者可以获得到目前为止高峰时期所使用的内存。所使用的内存以字节为单位。

<ol class="dp-j"><li class="alt"><span><span>$base_memory= memory_get_usage(); </span></span></li><li><span>echo <span class="string">"Hello,world!/n"</span><span>; </span></span></li><li class="alt"><span>$end_memory= memory_get_usage(); </span></li><li><span>$peak_memory= memory_get_peak_usage(); </span></li><li class="alt"><span>echo $base_memory,<span class="string">"/t"</span><span>,$end_memory,</span><span class="string">"/t"</span><span>,($end_memory-$base_memory),</span><span class="string">"/t"</span><span>, $peak_memory,</span><span class="string">"/n"</span><span>; </span></span></li></ol>

输出如下:

<ol class="dp-j"><li class="alt"><span><span>bash-</span><span class="number">3.2</span><span># phphelloworld.php </span></span></li><li class="alt"><span>Hello,world!  </span></li><li><span><span class="number">224400</span><span> </span><span class="number">224568</span><span> </span><span class="number">168</span><span> </span><span class="number">227424</span><span> </span></span></li></ol>

可以看到,即使程序中间只输出了一句话,再加上变量存储,也消耗了168个字节的内存。

对于同一程序,不同 PHP 版本对内存的使用并不相同,甚至还差别很大。

<ol class="dp-j"><li class="alt"><span><span>$baseMemory= memory_get_usage(); </span></span></li><li><span><span class="keyword">class</span><span> User </span></span></li><li class="alt"><span>{ </span></li><li><span><span class="keyword">private</span><span> $uid; </span></span></li><li class="alt"><span>function __construct($uid) </span></li><li><span>    { </span></li><li class="alt"><span>$<span class="keyword">this</span><span>->uid= $uid; </span></span></li><li><span>    } </span></li><li class="alt"><span>} </span></li><li><span> </span></li><li class="alt"><span><span class="keyword">for</span><span>($i=</span><span class="number">0</span><span>;$i<</span><span class="number">100000</span><span>;$i++) </span></span></li><li><span>{ </span></li><li class="alt"><span>$obj= <span class="keyword">new</span><span> User($i); </span></span></li><li><span><span class="keyword">if</span><span> ( $i% </span><span class="number">10000</span><span> === </span><span class="number">0</span><span> ) </span></span></li><li class="alt"><span>    { </span></li><li><span>echo sprintf( <span class="string">'%6d: '</span><span>, $i), memory_get_usage(), </span><span class="string">" bytes/n"</span><span>; </span></span></li><li class="alt"><span>    } </span></li><li><span>} </span></li><li class="alt"><span>echo <span class="string">"  peak: "</span><span>,memory_get_peak_usage(</span><span class="keyword">true</span><span>), </span><span class="string">" bytes/n"</span><span>; </span></span></li></ol>

在 PHP 5.2 中,内存使用如下:

<ol class="dp-j"><li class="alt"><span><span>[root</span><span class="annotation">@localhostphpperf</span><span>]# php52 memory.php </span></span></li><li><span> </span></li><li class="alt"><span><span class="number">0</span><span>: </span><span class="number">93784</span><span> bytes  </span></span></li><li><span><span class="number">10000</span><span>: </span><span class="number">93784</span><span> bytes  </span></span></li><li class="alt"><span>&hellip;&hellip; <span class="number">80000</span><span>: </span><span class="number">93784</span><span> bytes  </span></span></li><li><span><span class="number">90000</span><span>: </span><span class="number">93784</span><span> bytes  </span></span></li><li class="alt"><span>peak: <span class="number">262144</span><span> bytes </span></span></li></ol>

PHP 5.3 中,内存使用如下

<ol class="dp-j"><li class="alt"><span><span>[root</span><span class="annotation">@localhostphpperf</span><span>]# phpmemory.php </span></span></li><li><span> </span></li><li class="alt"><span><span class="number">0</span><span>: </span><span class="number">634992</span><span> bytes  </span></span></li><li><span><span class="number">10000</span><span>: </span><span class="number">634992</span><span> bytes  </span></span></li><li class="alt"><span>&hellip;&hellip; <span class="number">80000</span><span>: </span><span class="number">634992</span><span> bytes  </span></span></li><li><span><span class="number">90000</span><span>: </span><span class="number">634992</span><span> bytes  </span></span></li><li class="alt"><span>peak: <span class="number">786432</span><span> bytes </span></span></li></ol>

可见 PHP 5.3 在内存使用上要粗放了一些。

PHP 5.4 – 5.6 差不多,有所优化:

<ol class="dp-j"><li class="alt"><span><span>[root</span><span class="annotation">@localhostphpperf</span><span>]# php56 memory.php </span></span></li><li><span> </span></li><li class="alt"><span><span class="number">0</span><span>: </span><span class="number">224944</span><span> bytes  </span></span></li><li><span><span class="number">10000</span><span>: </span><span class="number">224920</span><span> bytes  </span></span></li><li class="alt"><span>&hellip;&hellip; <span class="number">80000</span><span>: </span><span class="number">224920</span><span> bytes  </span></span></li><li><span><span class="number">90000</span><span>: </span><span class="number">224920</span><span> bytes  </span></span></li><li class="alt"><span>peak: <span class="number">262144</span><span> bytes </span></span></li></ol>

而 PHP 7 在少量使用时,高峰内存的使用,增大很多。

<ol class="dp-c"><li class="alt"><span><span>[root@localhostphpperf]# php7 memory.php </span></span></li><li><span> </span></li><li class="alt"><span>0: 353912 bytes  </span></li><li><span>10000: 353912 bytes  </span></li><li class="alt"><span>&hellip;&hellip; 80000: 353912 bytes  </span></li><li><span>90000: 353912 bytes  </span></li><li class="alt"><span>peak: 2097152 bytes </span></li></ol>

从上面也看到,以上所使用的 PHP 都有比较好的垃圾回收机制,10万次初始化,并没有随着对象初始化的增多而增加内存的使用。PHP7 的高峰内存使用最多,达到了接近 2M。

下面再来看一个例子,在上面的代码的基础上,我们加上一行,如下:

$obj->self = $obj;

代码如下:

<ol class="dp-c"><li class="alt"><span><span class="vars">$baseMemory</span><span>= memory_get_usage(); </span></span></li><li><span><span class="keyword">class</span><span> User </span></span></li><li class="alt"><span>{ </span></li><li><span><span class="keyword">private</span><span> </span><span class="vars">$uid</span><span>; </span></span></li><li class="alt"><span><span class="keyword">function</span><span> __construct(</span><span class="vars">$uid</span><span>) </span></span></li><li><span>    { </span></li><li class="alt"><span><span class="vars">$this</span><span>->uid= </span><span class="vars">$uid</span><span>; </span></span></li><li><span>    } </span></li><li class="alt"><span>} </span></li><li><span> </span></li><li class="alt"><span><span class="keyword">for</span><span>(</span><span class="vars">$i</span><span>=0;</span><span class="vars">$i</span><span><100000;</span><span class="vars">$i</span><span>++) </span></span></li><li><span>{ </span></li><li class="alt"><span><span class="vars">$obj</span><span>= </span><span class="keyword">new</span><span> User(</span><span class="vars">$i</span><span>); </span></span></li><li><span><span class="vars">$obj</span><span>->self = </span><span class="vars">$obj</span><span>; </span></span></li><li class="alt"><span><span class="keyword">if</span><span> ( </span><span class="vars">$i</span><span>% 5000 === 0 ) </span></span></li><li><span>    { </span></li><li class="alt"><span><span class="func">echo</span><span> sprintf( </span><span class="string">'%6d: '</span><span>, </span><span class="vars">$i</span><span>), memory_get_usage(), </span><span class="string">" bytes/n"</span><span>; </span></span></li><li><span>    } </span></li><li class="alt"><span>} </span></li><li><span><span class="func">echo</span><span> </span><span class="string">"  peak: "</span><span>,memory_get_peak_usage(true), </span><span class="string">" bytes/n"</span><span>; </span></span></li></ol>

这时候再来看看内存的使用情况,中间表格主体部分为内存使用量,单位为字节。

图表如下:

PHP 5.2 并没有合适的垃圾回收机制,导致内存使用越来越多。而5.3 以后内存回收机制导致内存稳定在一个区间。而也可以看见 PHP7 内存使用最少。把 PHP 5.2 的图形去掉了之后,对比更为明显。

可见 PHP7 不仅是在算法效率上,有大幅度的提升,在大批量内存使用上也有大幅度的优化尽管小程序的高峰内存比历史版本所用内存更多)。

1.3、垃圾回收相关函数

在 PHP 中,内存回收是可以控制的,我们可以显式地关闭或者打开垃圾回收,一种方法是通过修改配置,zend.enable_gc=Off 就可以关掉垃圾回收。 缺省情况下是 On 的。另外一种手段是通过 gc _enable()和gc _disable()函数分别打开和关闭垃圾回收。

比如在上面的例子的基础上,我们关闭垃圾回收,就可以得到如下数据表格和图表。

代码如下:

<ol class="dp-c"><li class="alt"><span><span>gc_disable(); </span></span></li><li><span><span class="vars">$baseMemory</span><span>= memory_get_usage(); </span></span></li><li class="alt"><span><span class="keyword">class</span><span> User </span></span></li><li><span>{ </span></li><li class="alt"><span><span class="keyword">private</span><span> </span><span class="vars">$uid</span><span>; </span></span></li><li><span><span class="keyword">function</span><span> __construct(</span><span class="vars">$uid</span><span>) </span></span></li><li class="alt"><span>    { </span></li><li><span><span class="vars">$this</span><span>->uid= </span><span class="vars">$uid</span><span>; </span></span></li><li class="alt"><span>    } </span></li><li><span>} </span></li><li class="alt"><span> </span></li><li><span><span class="keyword">for</span><span>(</span><span class="vars">$i</span><span>=0;</span><span class="vars">$i</span><span><100000;</span><span class="vars">$i</span><span>++) </span></span></li><li class="alt"><span>{ </span></li><li><span><span class="vars">$obj</span><span>= </span><span class="keyword">new</span><span> User(</span><span class="vars">$i</span><span>); </span></span></li><li class="alt"><span><span class="vars">$obj</span><span>->self = </span><span class="vars">$obj</span><span>; </span></span></li><li><span><span class="keyword">if</span><span> ( </span><span class="vars">$i</span><span>% 5000 === 0 ) </span></span></li><li class="alt"><span>    { </span></li><li><span><span class="func">echo</span><span> sprintf( </span><span class="string">'%6d: '</span><span>, </span><span class="vars">$i</span><span>), memory_get_usage(), </span><span class="string">" bytes/n"</span><span>; </span></span></li><li class="alt"><span>    } </span></li><li><span>} </span></li><li class="alt"><span><span class="func">echo</span><span> </span><span class="string">"  peak: "</span><span>,memory_get_peak_usage(true), </span><span class="string">" bytes/n"</span><span>; </span></span></li></ol>

分别在 PHP 5.3、PHP5.4 、PHP5.5、PHP5.6 、PHP7 下运行,得到如下内存使用统计表。

图表如下,PHP7 还是内存使用效率最优的。

从上面的例子也可以看出来,尽管在第一个例子中,PHP7 的高峰内存使用数是最多的,但是当内存使用得多时,PHP7 的内存优化就体现出来了。

这里值得一提的是垃圾回收,尽管会使内存减少,但是会导致速度降低,因为垃圾回收也是需要消耗 CPU 等其他系统资源的。Composer 项目就曾经因为在计算依赖前关闭垃圾回收,带来成倍性能提升,引发广大网友关注。详见:

https://github.com/composer/composer/commit/ac676f47f7bbc619678a29deae097b6b0710b799

在常见的代码和性能分析中,出了以上三类函数之外,还常使用的有堆栈跟踪函数、输出函数,这里不再赘述。




Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn