遺憾的是,如上例所示,GC 有助長懶惰程式設計的傾向。
顯然,GC 在協助記憶體管理方面的優點是存在的,並且有助於維護系統穩定,但這並不是不妥善規劃和測試程式碼的藉口。
請務必以批判和客觀的態度重新閱讀您的程式碼,以確保您沒有無意中引入記憶體洩漏。
我們在前一節中已經提到,僅僅收集可能的根節點對效能的影響非常小,但這是將 PHP 5.2 與 PHP 5.3 進行比較的情況。儘管記錄可能的根節點比像 PHP 5.2 那樣完全不記錄要慢,但 PHP 5.3 中 PHP 執行環境的其他更改卻讓這種效能損失根本沒有顯現出來。
效能受影響的主要領域有兩個。第一個領域是減少記憶體使用量,第二個領域是垃圾回收機制執行記憶體清理時的執行時間延遲。我們將探討這兩個問題。
首先,實作垃圾回收機制的整個原因,是在滿足前提條件後,透過清理循環參考變數來減少記憶體使用量。在 PHP 的實作中,這會在根緩衝區滿時,或呼叫 gc_collect_cycles() 函式時發生。在下圖中,我們顯示了以下腳本在 PHP 5.2 和 PHP 5.3 中的記憶體使用情況,但不包括 PHP 本身啟動時使用的基本記憶體。
範例 #1 記憶體使用量範例
<?php
class Foo
{
public $var = '3.14159265359';
public $self;
}
$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 變數在迴圈的下一次迭代中被重新賦值時,通常會發生記憶體洩漏。在這種情況下,會洩漏兩個 zval 容器(物件 zval 和屬性 zval),但只找到一個可能的根:已取消設定的變數。當根緩衝區在 10,000 次迭代後滿了(總共有 10,000 個可能的根)時,垃圾回收機制就會啟動並釋放與這些可能的根關聯的記憶體。這可以很清楚地從 PHP 5.3 鋸齒狀的記憶體使用圖表中看到。每 10,000 次迭代後,該機制就會啟動並釋放與循環參考變數關聯的記憶體。在此範例中,該機制本身不需要做很多工作,因為洩漏的結構非常簡單。從圖中可以看出,PHP 5.3 的最大記憶體使用量約為 9 Mb,而 PHP 5.2 的記憶體使用量則持續增加。
垃圾回收機制影響效能的第二個方面是垃圾回收機制啟動釋放「洩漏」記憶體所需的時間。為了瞭解這是多少,我們稍微修改了之前的腳本,允許更大數量的迭代和移除中間記憶體使用數據。第二個腳本如下:
範例 #2 垃圾回收效能影響
<?php
class Foo
{
public $var = '3.14159265359';
public $self;
}
for ( $i = 0; $i <= 1000000; $i++ )
{
$a = new Foo;
$a->self = $a;
}
echo memory_get_peak_usage(), "\n";
?>
我們將執行此腳本兩次,一次開啟 zend.enable_gc 設定,一次關閉它。
範例 #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
在我的機器上,第一個指令似乎持續需要大約 10.7 秒,而第二個指令則需要大約 11.4 秒。這大約減慢了 7%。然而,腳本使用的最大記憶體量減少了 98%,從 931Mb 減少到 10Mb。這個基準測試不是很科學,甚至不能代表實際應用程式,但它確實展示了此垃圾回收機制提供的記憶體使用優勢。好處是,對於這個特定腳本,效能降低始終是相同的 7%,而記憶體節省能力會隨著在腳本執行期間發現更多循環參考而節省越來越多的記憶體。
可以從 PHP 內部 coax 出更多關於垃圾回收機制如何運作的資訊。但為了做到這一點,您必須重新編譯 PHP 以啟用基準測試和數據收集程式碼。您必須在使用所需選項執行 ./configure
之前,將 CFLAGS
環境變數設定為 -DGC_BENCH=1
。以下步驟應該可以做到這一點
範例 #4 重新編譯 PHP 以啟用 GC 基準測試
export CFLAGS=-DGC_BENCH=1 ./config.nice make clean make
當您使用新建立的 PHP 二進位檔再次執行上述範例程式碼時,您將會在 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
最有用的統計資料顯示在第一個區塊中。您可以看到垃圾回收機制執行了 110 次,在這 110 次執行中,總共釋放了超過 200 萬個記憶體配置。一旦垃圾回收機制至少執行一次,「Root buffer peak」永遠是 10000。
一般來說,PHP 中的垃圾回收器只會在循環收集演算法實際運行時造成速度減慢,而在普通的(較小的)腳本中,則完全不會影響效能。
然而,在循環收集機制確實針對普通腳本運行的情況下,它提供的記憶體減少允許更多此類腳本在您的伺服器上同時運行,因為總共使用的記憶體量減少了。
對於長時間運行的腳本,例如冗長的測試套件或守護行程腳本,其優點最為明顯。此外,對於通常比網頁腳本運行時間更長的 » PHP-GTK 應用程式,新的機制應該會對隨著時間推移而潛入的記憶體洩漏產生相當大的影響。
遺憾的是,如上例所示,GC 有助長懶惰程式設計的傾向。
顯然,GC 在協助記憶體管理方面的優點是存在的,並且有助於維護系統穩定,但這並不是不妥善規劃和測試程式碼的藉口。
請務必以批判和客觀的態度重新閱讀您的程式碼,以確保您沒有無意中引入記憶體洩漏。
可以不必重新編譯 PHP 就取得 GC 效能統計資料。從 Xdebug 2.6 版開始,您可以啟用統計資料收集到檔案中(預設目錄為 /tmp,檔案名稱為 gcstats.%p)
php -dxdebug.gc_stats_enable=1 your_script.php