2009-07-17 11 views
15

今後のメモリリークを避けるために、PHPプログラム(Drupalモジュールなど)でメモリをリークする簡単なPHPスクリプトを使いこなしています。なぜこの単純なPHPスクリプトはメモリをリークしますか?

PHPの専門家が、このスクリプトがメモリ使用量を継続的に増加させる原因を見つけるのに役立つでしょうか?

さまざまなパラメータを変更して、自分で実行してみてください。結果は面白いです。ここでは、次のとおりです。

私にとって
<?php 

function memstat() { 
    print "current memory usage: ". memory_get_usage() . "\n"; 
} 

function waste_lots_of_memory($iters) { 
    $i = 0; 
    $object = new StdClass; 
    for (;$i < $iters; $i++) { 
    $object->{"member_" . $i} = array("blah blah blha" => 12345); 
    $object->{"membersonly_" . $i} = new StdClass; 
    $object->{"onlymember"} = array("blah blah blha" => 12345); 
    } 
    unset($object); 
} 

function waste_a_little_less_memory($iters) { 
    $i = 0; 
    $object = new StdClass; 
    for (;$i < $iters; $i++) { 

    $object->{"member_" . $i} = array("blah blah blha" => 12345); 
    $object->{"membersonly_" . $i} = new StdClass; 
    $object->{"onlymember"} = array("blah blah blha" => 12345); 

    unset($object->{"membersonly_". $i}); 
    unset($object->{"member_" . $i}); 
    unset($object->{"onlymember"}); 

    } 
    unset($object); 
} 

memstat(); 

waste_a_little_less_memory(1000000); 

memstat(); 

waste_lots_of_memory(10000); 

memstat(); 

、出力は次のようになります。

current memory usage: 73308 
current memory usage: 74996 
current memory usage: 506676 

[未設定の複数のオブジェクトのメンバーに編集した]

+0

問題を切り分けるために、一度にforループの行を削除しようとします。 –

答えて

34

unset()変数で使用するメモリを解放しません。メモリは、 "ガベージコレクタ"(PHPがバージョン5.3.0より前の実際のガベージコレクタを持っていなかったので引用符で囲まれています。プリミティブで主に動作するメモリフリールーチン)は適合しています。

また、技術的には、unset()に電話する必要はありません。$objectという変数は、機能の範囲に限定されているためです。

ここに、その違いを示すスクリプトがあります。私はあなたのmemstat()関数を修正して、最後の呼び出し以降のメモリの違いを表示しました。

<?php 
function memdiff() { 
    static $int = null; 

    $current = memory_get_usage(); 

    if ($int === null) { 
     $int = $current; 
    } else { 
     print ($current - $int) . "\n"; 
     $int = $current; 
    } 
} 

function object_no_unset($iters) { 
    $i = 0; 
    $object = new StdClass; 

    for (;$i < $iters; $i++) { 
     $object->{"member_" . $i}= array("blah blah blha" => 12345); 
     $object->{"membersonly_" . $i}= new StdClass; 
     $object->{"onlymember"}= array("blah blah blha" => 12345); 
    } 
} 

function object_parent_unset($iters) { 
    $i = 0; 
    $object = new StdClass; 

    for (;$i < $iters; $i++) { 
     $object->{"member_" . $i}= array("blah blah blha" => 12345); 
     $object->{"membersonly_" . $i}= new StdClass; 
     $object->{"onlymember"}= array("blah blah blha" => 12345); 
    } 

    unset ($object); 
} 

function object_item_unset($iters) { 
    $i = 0; 
    $object = new StdClass; 

    for (;$i < $iters; $i++) { 

     $object->{"member_" . $i}= array("blah blah blha" => 12345); 
     $object->{"membersonly_" . $i}= new StdClass; 
     $object->{"onlymember"}= array("blah blah blha" => 12345); 

     unset ($object->{"membersonly_" . $i}); 
     unset ($object->{"member_" . $i}); 
     unset ($object->{"onlymember"}); 
    } 
    unset ($object); 
} 

function array_no_unset($iters) { 
    $i = 0; 
    $object = array(); 

    for (;$i < $iters; $i++) { 
     $object["member_" . $i] = array("blah blah blha" => 12345); 
     $object["membersonly_" . $i] = new StdClass; 
     $object["onlymember"] = array("blah blah blha" => 12345); 
    } 
} 

function array_parent_unset($iters) { 
    $i = 0; 
    $object = array(); 

    for (;$i < $iters; $i++) { 
     $object["member_" . $i] = array("blah blah blha" => 12345); 
     $object["membersonly_" . $i] = new StdClass; 
     $object["onlymember"] = array("blah blah blha" => 12345); 
    } 
    unset ($object); 
} 

function array_item_unset($iters) { 
    $i = 0; 
    $object = array(); 

    for (;$i < $iters; $i++) { 
     $object["member_" . $i] = array("blah blah blha" => 12345); 
     $object["membersonly_" . $i] = new StdClass; 
     $object["onlymember"] = array("blah blah blha" => 12345); 

     unset ($object["membersonly_" . $i]); 
     unset ($object["member_" . $i]); 
     unset ($object["onlymember"]); 
    } 
    unset ($object); 
} 

$iterations = 100000; 

memdiff(); // Get initial memory usage 

object_item_unset ($iterations); 
memdiff(); 

object_parent_unset ($iterations); 
memdiff(); 

object_no_unset ($iterations); 
memdiff(); 

array_item_unset ($iterations); 
memdiff(); 

array_parent_unset ($iterations); 
memdiff(); 

array_no_unset ($iterations); 
memdiff(); 
?> 

あなたがオブジェクトを使用している場合は、必ずクラスがunset()に適切に明確なリソースを可能にするために__unset()を実装します。可能であれば、stdClassなどの可変構造クラスの使用を避けるか、クラステンプレートに配置されていないメンバーに値を割り当てることをお勧めします。クラステンプレートに割り当てられたメモリは通常適切にクリアされません。

PHP 5.3.0以降ではガベージコレクタが改善されていますが、デフォルトでは無効になっています。有効にするには、gc_enable()を一度呼び出す必要があります。

+0

@mjgoins:私はより多くの情報で投稿を編集しました。デフォルトのメモリコレクタは、プリミティブ型で最も効果的です。リソースとオブジェクトをミックスに導入し始めると、すぐに失敗します。 –

+0

答えはphp 5.3までで、php *は固定メモリ空間で任意に長いジョブを実行できません。メモリのリークが少ない機能であっても、メモリの割り当てが高い場合は数日かかることがありますが、メモリのリークが少ない機能も徐々に使い果たされます。 – mjgoins

+0

@mjgoins:私は、1分から数時間のうちに何時間もかかるかもしれない複数のタスクを実行します。トリックは、オブジェクトを避けるか、必要ならばそれらを再利用しようとし、プリミティブに固執することです(あなたの例では、簡単に配列を使うことができます)。 –

2

memory_get_usage()の私の理解では、出力が依存できるということですオペレーティングシステムとバージョンファクタの広い範囲。

さらに重要なことに、変数を設定解除しても、即座にメモリが解放されず、プロセスから割り当てが解除され、オペレーティングシステムに戻されます(この操作の特性はオペレーティングシステムによって異なります)。

要するに、メモリリークを調べるために、より複雑な設定が必要な場合があります。

0

PHPでの正確な動作についてはわかりませんが、他の言語では他のオブジェクトを含むオブジェクトをnullに設定しても、他のオブジェクトはnullに設定されません。これらのオブジェクトへの参照は終了しますが、PHPはJavaの意味で「ガベージコレクション」を持たないため、サブオブジェクトは個別に削除されるまでメモリに存在します。

+0

誰かがこの問題に遭遇する場合、オブジェクト(または他の値)に参照がないときにメモリを解放するプロセスは、PHPではガベージコレクタとはみなされません。機能の別の部分が、それは起こらないことを意味しません。変数への参照は数えられ、 'unset()'などはその数を1つ減らします。カウントがゼロになると、メモリはすぐにPHPメモリマネージャー内で解放され、新しい変数で使用できる状態になります。 – IMSoP

3

memory_get_usageは、phpがosから割り当てたメモリ量を報告します。使用中のすべての変数のサイズと必ずしも一致しません。 PHPがメモリを最大限に使用している場合、使用していない量のメモリを直ちに返さないことになります。あなたの例では、関数waste_a_little_less_memoryは、未使用の変数を時間の経過とともに取り消します。したがって、ピーク使用量は比較的少ない。 waste_lots_of_memoryは、割り当てを解除する前に多くの変数(使用済みメモリがたくさんあります)を構築します。したがって、ピーク使用量ははるかに大きくなります。「バイト単位で、それは現在のPHPスクリプトに割り当てられていますメモリの量を返します

22

memory_get_usage()

OSによってプロセスに割り当てられたメモリの量だ、のない量割り当てられた変数によって使用されるメモリ。 PHPは常にメモリをOSに戻すわけではありませんが、新しい変数が割り当てられてもメモリは再利用できます。

これは簡単です。

memstat(); 
waste_lots_of_memory(10000); 
memstat(); 
waste_lots_of_memory(10000); 
memstat(); 

正しい場合、PHPが実際にメモリをリークしている場合は、メモリ使用率が2倍になるはずです。 waste_lots_of_memoryの最初の呼び出し(後に「解放された」メモリ)が第2の呼び出しで再使用されているため、

current memory usage: 88272 
current memory usage: 955792 
current memory usage: 955808 

これは、次のとおりです。ただし、ここでの実際の結果です。

私の5年間のPHPで、数百万のオブジェクトと数ギガバイトのデータを数時間にわたって処理したスクリプトと、一度に何ヵ月も実行していたスクリプトを作成しました。 PHPのメモリ管理はそれほど素晴らしいものではありませんが、それほど難しくありません。

+0

これは質問への正解であり、それ以上のアップフォースが必要です。 – IMSoP

0

memory_get_usage()は即時のメモリ使用量を返しませんが、プロセスを実行するためにメモリを格納します。巨大な配列解除($のARRAY_A)の場合に は...もっと私のシステムではmemory_get_usage()に応じてメモリを解放しますが消費しません

$array_a="(SOME big array)"; 
$array_b=""; 
//copy array_a to array_b 
for($line=0; $line<100;$line++){ 
$array_b[$line]=$array_a[$line]; 
} 

unset($array_a); //having this shows actually a bigger consume 
print_r($array_b); 

エコーmemory_get_usage();

関連する問題