PHP的垃圾回收機制-效能方面考慮的因素

柳旦旦發表於2021-01-14

效能方面考慮的因素

在上一節我們已經簡單的提到:回收可能根有細微的效能上影響,但這是把PHP 5.2與PHP 5.3比較時才有的。儘管在PHP 5.2中,記錄可能根相對於完全不記錄可能根要慢些,而PHP 5.3中對 PHP run-time 的其他修改減少了這個效能損失。

這裡主要有兩個領域對效能有影響。第一個是記憶體佔用空間的節省,另一個是垃圾回收機制執行記憶體清理時的執行時間增加(run-time delay)。我們將研究這兩個領域。

記憶體佔用空間的節省

首先,實現垃圾回收機制的整個原因是為了,一旦先決條件滿足,通過清理迴圈引用的變數來節省記憶體佔用。在PHP執行中,一旦根緩衝區滿了或者呼叫gc_collect_cycles() 函式時,就會執行垃圾回收。在下圖中,顯示了下面指令碼分別在PHP 5.2 和 PHP 5.3環境下的記憶體佔用情況,其中排除了指令碼啟動時PHP本身佔用的基本記憶體。

Example #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";
    }
}
?>

Comparison of memory usage between PHP 5.2 and PHP 5.3

在這個很理論性的例子中,我們建立了一個物件,這個物件中的一個屬性被設定為指回物件本身。在迴圈的下一個重複(iteration)中,當指令碼中的變數被重新複製時,就會發生典型性的記憶體洩漏。在這個例子中,兩個變數容器是洩漏的(物件容器和屬性容器),但是僅僅能找到一個可能根:就是被unset的那個變數。在10,000次重複後(也就產生總共10,000個可能根),當根緩衝區滿時,就執行垃圾回收機制,並且釋放那些關聯的可能根的記憶體。這從PHP 5.3的鋸齒型記憶體佔用圖中很容易就能看到。每次執行完10,000次重複後,執行垃圾回收,並釋放相關的重複使用的引用變數。在這個例子中由於洩漏的資料結構非常簡單,所以垃圾回收機制本身不必做太多工作。從這個圖表中,你能看到 PHP 5.3的最大記憶體佔用大概是9 Mb,而PHP 5.2的記憶體佔用一直增加。

執行時間增加(Run-Time Slowdowns)

垃圾回收影響效能的第二個領域是它釋放已洩漏的記憶體耗費的時間。為了看到這個耗時時多少,我們稍微改變了上面的指令碼,有更多次數的重複並且刪除了迴圈中的記憶體佔用計算,第二個指令碼程式碼如下:

Example #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";
?>

我們將執行這個指令碼兩次,一次通過配置zend.enable_gc開啟垃圾回收機制時,另一次是它關閉時。

Example #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內部 GC 統計資訊

在PHP內部,可以顯示更多的關於垃圾回收機制如何執行的資訊。但是要顯示這些資訊,你需要先重新編譯PHP使benchmark和data-collecting code可用。你需要在按照你的意願執行./configure前,把環境變數CFLAGS設定成-DGC_BENCH=1。下面的命令串就是做這個事:

Example #4 重新編譯PHP以啟用GC benchmarking

export CFLAGS=-DGC_BENCH=1
./config.nice
make clean
make

當你用新編譯的PHP二進位制檔案來重新執行上面的例子程式碼,在PHP執行結束後,你將看到下面的資訊:

Example #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次執行中,總共有超過兩百萬的記憶體分配被釋放。只要垃圾回收機制執行了至少一次,根緩衝區峰值(Root buffer peak)總是10000.

結論

通常,PHP中的垃圾回收機制,僅僅在迴圈回收演算法確實執行時會有時間消耗上的增加。但是在平常的(更小的)指令碼中應根本就沒有效能影響。

然而,在平常指令碼中有迴圈回收機制執行的情況下,記憶體的節省將允許更多這種指令碼同時執行在你的伺服器上。因為總共使用的記憶體沒達到上限。

這種好處在長時間執行指令碼中尤其明顯,諸如長時間的測試套件或者daemon指令碼此類。同時,對通常比Web指令碼執行時間長的» PHP-GTK應用程式,新的垃圾回收機制,應該會大大改變一直以來認為記憶體洩漏問題難以解決的看法。

本作品採用《CC 協議》,轉載必須註明作者和本文連結
專注細節,慢慢提升自己。✍️

相關文章