PHP垃圾回收之性能
jopen
13年前
<p>关于PHP垃圾回收机制(Garbage Collection . GC) ,原作者写了三篇文章。<br /> 第一篇,主要讲解<a title="PHP垃圾回收机制 之 变量的处理" href="/misc/goto?guid=4959499537078654175" target="_blank">PHP如何处理变量</a>。<br /> 第二篇主要讲<a title="PHP垃圾回收机制 之 如何进行垃圾回收" href="/misc/goto?guid=4959499537182182295" target="_blank">常用的GC方法,以及GC是如何实现的</a>。<br /> 这是第三篇,主要从性能方面来考虑垃圾回收。</p> <p>原文: http://derickrethans.nl/collecting-garbage-performance-considerations.html</p> <p>在上二篇文章中提到了circular reference(环形引用)以及如何对其进行垃圾回收,<br /> 不可否认,垃圾回收的过程消耗了一定的时间,会对性能产生一定的影响。<br /> 上一篇文章说到把数组变量的zval容量收集到root buffer的速度还是挺快的,<br /> 其实这是因为php5.3 runtime有了一些改进,使得这个收集的过程对整体性能影响不大。</p> <p>提到一种算法,我们一般都会考虑到它使用的空间与时间。<br /> PHP垃圾回收机制对性能的影响,也从这两个方面来看:<br /> 一是减少了多少内存占用;二是额外消耗了多少时间。</p> <h2>垃圾回收机制可以减少多少内存占用</h2> <p>当root buffer充满的时候(默认需要一万个zval容器),或者当gc_collect_cyles()被调用的时候,PHP会启动垃圾回收。<br /> 下面的图表,显示了一段代码在php5.2(没有垃圾回收)与php5.3(有垃圾回收)环境下运行时占用的内存。<br /> 代码如下:</p> <pre class="brush:php; toolbar: true; auto-links: false;"><?php class Foo { public $var = '3.1415962654'; } $baseMemory = memory_get_usage(); for ( $i = 0; $i <= 100000; $i++ ) { $a = new Foo; $a->self = $a; if ( $i % 500 === 0 ) { echo sprintf( '%8d: ', $i ), memory_get_usage() - $baseMemory, "\n"; } } ?></pre> <img title="PHP垃圾回收之性能" border="0" alt="PHP垃圾回收之性能" src="https://simg.open-open.com/show/f56b0c822a180007b519d1217e61bc9c.png" width="600" height="339" /> <br /> 在这段代码中,我们创建了一个对象$a,然后让$a的属性self指向了$a。 <br /> 当$a在下一次循环中被重新new出来的时候,内存泄露就出现了。 <br /> 如果这里不太理解,请回去看第一篇文件《 <a title="PHP垃圾回收机制 之 变量的处理" href="/misc/goto?guid=4959499537078654175">PHP垃圾回收机制 之 变量的处理</a>》 <br /> 在这种情况下,有二个zval容器所占用的空间无法回收,一个是$a本身,另外一个是$a->var。 <br /> 但是只有$a的zval会被收集到root buffer中。 <br /> 当root buffer收集到10000个zval的时候,垃圾回收启动,清理掉这10000个zval,以及它们的子zval。一共就清理掉了20000个zval。 <br /> 在图中可以看得很清楚,每当达到10000次循环,内存的占用量就减少一次。 <br /> 最终可以看到,在PHP5.3中内存最多占用为9M,而在php5.2中,内存占用为90M。 <br /> 如果程序继续循环下去,php5.3中内存还是最多占用9M,在php5.2中,内存占用会一直增长上去。 <h2>垃圾回收机制会额外消耗多少时间</h2> <p>我们把上面的PHP代码修改一下,让它循环更多次。</p> <div> <div> <pre><?php class Foo { public $var = '3.1415962654'; } for ( $i = 0; $i <= 1000000; $i++ ) { $a = new Foo; $a->self = $a; } echo memory_get_peak_usage(), "\n"; ?></pre> </div> </div> <p>接下来运行这段代码二次。一次关闭gc,另外一次打开gc。</p> <div> <div> <pre>time ~/dev/php/php-5.3dev/sapi/cli/php -dzend.enable_gc=0 \ -dmemory_limit=-1 -n part3-example2.php # and time ~/dev/php/php-5.3dev/sapi/cli/php -dzend.enable_gc=1 \ -dmemory_limit=-1 -n part3-example2.php</pre> </div> </div> <p>在我的测试机上,第一条命令使用了10.7秒,第二条命令使用了11.4秒,大约多使用了7%的时间。<br /> 内存占用上,第一次为931M,第二次10M,节省了98%。<br /> 这次的评测虽然并不是很科学,或者也不能代表实际应用中的情况,但从中还是可以很明显地看到garbage collection对于内存的节省。<br /> 如果循环更多次,时间的额外消耗仍然会是7%,但是内存的节省就可能会是99%了,甚至会很趋近于100%。</p> <h2>php内部的garbage collection 统计数据</h2> <p>php源代码中已经有一定的gc统计功能了,不过默认是关闭着的。<br /> 如果要使用这个统计功能,就要重新编译PHP:</p> <div> <div> <pre>export CFLAGS=GC_BENCH=1 ./config.nice make clean make</pre> </div> </div> <p>然后运行上面的例子代码,运行结束时,就可以看到如下的信息了:</p> <div> <div> <pre>GC Statistics ------------- Runs: 110 Collected: 2072204 Root buffer length: 0 Root buffer peak: 10000 Possible Remove from Marked Root Buffered buffer grey -------- -------- ----------- ------ ZVAL 7175487 1491291 1241690 3611871 ZOBJ 28506264 1527980 677581 1025731</pre> </div> </div> <p>上面的信息说明垃圾回收运行了110次,累计回收了2M左右的内存,root buffer的峰值为10000。这个是显然的。</p> <h2>结论</h2> <p>在这最后一篇文章里面,我们简单看了一下php5.3中垃圾回收对于PHP性能的影响。<br /> 在一般情况下,只有真正在进行垃圾回收的时候,才会额外消耗一部分时间。<br /> 所以如果代码运行时间较短,垃圾回收甚至都不会运行,对性能也不会产生什么影响。</p> <p>如果垃圾回收真的运行了,那么它节省下来的内存,也可以使得web服务器同时运行更多的程序。</p> <p>垃圾回收真正发挥自己优势的地方在于长时间运行的脚本,比如测试用例啊,或者守护程序啊,或者php-gtk的界面程序之类的。</p> <p></p>