在上一節我們已經(jīng)簡(jiǎn)單的提到:回收可能根有細微的性能上影響,但這是把PHP 5.2與PHP 5.3比較時(shí)才有的。盡管在PHP 5.2中,記錄可能根相對于完全不記錄可能根要慢些,而PHP 5.3中對 PHP run-time 的其他修改減少了這個(gè)性能損失。
這里主要有兩個(gè)領(lǐng)域對性能有影響。第一個(gè)是內存占用空間的節省,另一個(gè)是垃圾回收機制執行內存清理時(shí)的執行時(shí)間增加(run-time delay)。我們將研究這兩個(gè)領(lǐng)域。
首先,實(shí)現垃圾回收機制的整個(gè)原因是為了,一旦先決條件滿(mǎn)足,通過(guò)清理循環(huán)引用的變量來(lái)節省內存占用。在PHP執行中,一旦根緩沖區滿(mǎn)了或者調用gc_collect_cycles() 函數時(shí),就會(huì )執行垃圾回收。在下圖中,顯示了下面腳本分別在PHP 5.2 和 PHP 5.3環(huán)境下的內存占用情況,其中排除了腳本啟動(dòng)時(shí)PHP本身占用的基本內存。
示例 #1 Memory usage example
<?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";
}
}
?>
在這個(gè)很理論性的例子中,我們創(chuàng )建了一個(gè)對象,這個(gè)對象中的一個(gè)屬性被設置為指回對象本身。在循環(huán)的下一個(gè)重復(iteration)中,當腳本中的變量被重新復制時(shí),就會(huì )發(fā)生典型性的內存泄漏。在這個(gè)例子中,兩個(gè)變量容器是泄漏的(對象容器和屬性容器),但是僅僅能找到一個(gè)可能根:就是被unset的那個(gè)變量。在10,000次重復后(也就產(chǎn)生總共10,000個(gè)可能根),當根緩沖區滿(mǎn)時(shí),就執行垃圾回收機制,并且釋放那些關(guān)聯(lián)的可能根的內存。這從PHP 5.3的鋸齒型內存占用圖中很容易就能看到。每次執行完10,000次重復后,執行垃圾回收,并釋放相關(guān)的重復使用的引用變量。在這個(gè)例子中由于泄漏的數據結構非常簡(jiǎn)單,所以垃圾回收機制本身不必做太多工作。從這個(gè)圖表中,你能看到 PHP 5.3的最大內存占用大概是9 Mb,而PHP 5.2的內存占用一直增加。
垃圾回收影響性能的第二個(gè)領(lǐng)域是它釋放已泄漏的內存耗費的時(shí)間。為了看到這個(gè)耗時(shí)時(shí)多少,我們稍微改變了上面的腳本,有更多次數的重復并且刪除了循環(huán)中的內存占用計算,第二個(gè)腳本代碼如下:
示例 #2 GC性能影響
<?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";
?>
我們將運行這個(gè)腳本兩次,一次通過(guò)配置zend.enable_gc打開(kāi)垃圾回收機制時(shí),另一次是它關(guān)閉時(shí)。
示例 #3 執行以上腳本
time php -dzend.enable_gc=0 -dmemory_limit=-1 -n example2.php # and time php -dzend.enable_gc=1 -dmemory_limit=-1 -n example2.php
在我的機器上,第一個(gè)命令持續執行時(shí)間大概為10.7秒,而第二個(gè)命令耗費11.4秒。時(shí)間上增加了7%。然而,執行這個(gè)腳本時(shí)內存占用的峰值降低了98%,從931Mb 降到 10Mb。這個(gè)基準不是很科學(xué),或者并不能代表真實(shí)應用程序的數據,但是它的確顯示了垃圾回收機制在內存占用方面的好處。好消息就是,對這個(gè)腳本而言,在執行中出現更多的循環(huán)引用變量時(shí),內存節省的更多的情況下,每次時(shí)間增加的百分比都是7%。
在PHP內部,可以顯示更多的關(guān)于垃圾回收機制如何運行的信息。但是要顯示這些信息,你需要先重新編譯PHP使benchmark和data-collecting code可用。你需要在按照你的意愿運行./configure
前,把環(huán)境變量CFLAGS
設置成-DGC_BENCH=1
。下面的命令串就是做這個(gè)事:
示例 #4 重新編譯PHP以啟用GC benchmarking
export CFLAGS=-DGC_BENCH=1 ./config.nice make clean make
當你用新編譯的PHP二進(jìn)制文件來(lái)重新執行上面的例子代碼,在PHP執行結束后,你將看到下面的信息:
示例 #5 GC 統計數據
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
主要的信息統計在第一個(gè)塊。你能看到垃圾回收機制運行了110次,而且在這110次運行中,總共有超過(guò)兩百萬(wàn)的內存分配被釋放。只要垃圾回收機制運行了至少一次,根緩沖區峰值(Root buffer peak)總是10000.
通常,PHP中的垃圾回收機制,僅僅在循環(huán)回收算法確實(shí)運行時(shí)會(huì )有時(shí)間消耗上的增加。但是在平常的(更小的)腳本中應根本就沒(méi)有性能影響。
然而,在平常腳本中有循環(huán)回收機制運行的情況下,內存的節省將允許更多這種腳本同時(shí)運行在你的服務(wù)器上。因為總共使用的內存沒(méi)達到上限。
這種好處在長(cháng)時(shí)間運行腳本中尤其明顯,諸如長(cháng)時(shí)間的測試套件或者daemon腳本此類(lèi)。同時(shí),對通常比Web腳本運行時(shí)間長(cháng)的? PHP-GTK應用程序,新的垃圾回收機制,應該會(huì )大大改變一直以來(lái)認為內存泄漏問(wèn)題難以解決的看法。