PHP垃圾回收之性能
關于PHP垃圾回收機制(Garbage Collection . GC) ,原作者寫了三篇文章。
第一篇,主要講解PHP如何處理變量。
第二篇主要講常用的GC方法,以及GC是如何實現的。
這是第三篇,主要從性能方面來考慮垃圾回收。
原文: http://derickrethans.nl/collecting-garbage-performance-considerations.html
在上二篇文章中提到了circular reference(環形引用)以及如何對其進行垃圾回收,
不可否認,垃圾回收的過程消耗了一定的時間,會對性能產生一定的影響。
上一篇文章說到把數組變量的zval容量收集到root buffer的速度還是挺快的,
其實這是因為php5.3 runtime有了一些改進,使得這個收集的過程對整體性能影響不大。
提到一種算法,我們一般都會考慮到它使用的空間與時間。
PHP垃圾回收機制對性能的影響,也從這兩個方面來看:
一是減少了多少內存占用;二是額外消耗了多少時間。
垃圾回收機制可以減少多少內存占用
當root buffer充滿的時候(默認需要一萬個zval容器),或者當gc_collect_cyles()被調用的時候,PHP會啟動垃圾回收。
下面的圖表,顯示了一段代碼在php5.2(沒有垃圾回收)與php5.3(有垃圾回收)環境下運行時占用的內存。
代碼如下:
<?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"; } } ?>

在這段代碼中,我們創建了一個對象$a,然后讓$a的屬性self指向了$a。
當$a在下一次循環中被重新new出來的時候,內存泄露就出現了。
如果這里不太理解,請回去看第一篇文件《 PHP垃圾回收機制 之 變量的處理》
在這種情況下,有二個zval容器所占用的空間無法回收,一個是$a本身,另外一個是$a->var。
但是只有$a的zval會被收集到root buffer中。
當root buffer收集到10000個zval的時候,垃圾回收啟動,清理掉這10000個zval,以及它們的子zval。一共就清理掉了20000個zval。
在圖中可以看得很清楚,每當達到10000次循環,內存的占用量就減少一次。
最終可以看到,在PHP5.3中內存最多占用為9M,而在php5.2中,內存占用為90M。
如果程序繼續循環下去,php5.3中內存還是最多占用9M,在php5.2中,內存占用會一直增長上去。
垃圾回收機制會額外消耗多少時間
我們把上面的PHP代碼修改一下,讓它循環更多次。
<?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"; ?>
接下來運行這段代碼二次。一次關閉gc,另外一次打開gc。
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
在我的測試機上,第一條命令使用了10.7秒,第二條命令使用了11.4秒,大約多使用了7%的時間。
內存占用上,第一次為931M,第二次10M,節省了98%。
這次的評測雖然并不是很科學,或者也不能代表實際應用中的情況,但從中還是可以很明顯地看到garbage collection對于內存的節省。
如果循環更多次,時間的額外消耗仍然會是7%,但是內存的節省就可能會是99%了,甚至會很趨近于100%。
php內部的garbage collection 統計數據
php源代碼中已經有一定的gc統計功能了,不過默認是關閉著的。
如果要使用這個統計功能,就要重新編譯PHP:
export CFLAGS=GC_BENCH=1 ./config.nice make clean make
然后運行上面的例子代碼,運行結束時,就可以看到如下的信息了:
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
上面的信息說明垃圾回收運行了110次,累計回收了2M左右的內存,root buffer的峰值為10000。這個是顯然的。
結論
在這最后一篇文章里面,我們簡單看了一下php5.3中垃圾回收對于PHP性能的影響。
在一般情況下,只有真正在進行垃圾回收的時候,才會額外消耗一部分時間。
所以如果代碼運行時間較短,垃圾回收甚至都不會運行,對性能也不會產生什么影響。
如果垃圾回收真的運行了,那么它節省下來的內存,也可以使得web服務器同時運行更多的程序。
垃圾回收真正發揮自己優勢的地方在于長時間運行的腳本,比如測試用例啊,或者守護程序啊,或者php-gtk的界面程序之類的。