web-dev-qa-db-ja.com

PHPでメモリを強制的に解放する

PHPプログラムでは、一連のファイルを順番に読み取ります(file_get_contents)、gzdecodeそれら、json_decode結果を取得し、内容を分析し、その大部分を捨て、約1%を配列に格納します。

残念ながら、各反復(ファイル名を含む配列を走査します)では、メモリが失われているようです(memory_get_peak_usage、毎回約2〜10 MB)。コードをダブルおよびトリプルチェックしました。不要なデータをループに保存していません(必要なデータが全体で約10MBを超えることはほとんどありません)が、頻繁に書き換えています(実際には、配列内の文字列)。どうやら、PHPはメモリを正しく解放しないので、制限に達するまでますますRAMを使用します。

強制的なガベージコレクションを行う方法はありますか?または、少なくとも、メモリが使用されている場所を見つけるには?

53
DBa

メモリの断片化に関係しています。

1つの文字列に連結された2つの文字列を考えます。各オリジナルは、出力が作成されるまで保持する必要があります。出力は、どちらの入力よりも長くなります。
したがって、このような連結の結果を保存するために、新しい割り当てを行う必要があります。元の文字列解放されますですが、メモリの小さなブロックです。
'str1' . 'str2' . 'str3' . 'str4'の場合、それぞれに複数のtempが作成されます。 -そして、それらのどれもが解放されたスペースに収まりません。文字列は、メモリの他の用途のために、連続したメモリにレイアウトされない可能性があります(つまり、各文字列はエンドツーエンドでレイアウトされません)。そのため、スペースを効果的に再利用できないため、文字列を解放すると問題が発生します。したがって、作成するtmpごとに成長します。そして、あなたは何も再利用しません。

配列ベースの内破を使用すると、必要な長さの1つの出力のみを作成します。追加の割り当てを1つだけ実行します。そのため、メモリ効率が大幅に向上し、連結フラグメンテーションの影響を受けません。 Pythonでも同じことが言えます。文字列を連結する必要がある場合は、常に複数の連結を配列ベースにする必要があります。

''.join(['str1','str2','str3'])

pythonで

implode('', array('str1', 'str2', 'str3'))

pHPで

sprintfと同等のものでも問題ありません。

Memory_get_peak_usageによって報告されるメモリは、基本的に、常に使用する必要のある仮想マップのメモリの「最後の」ビットです。そのため、常に成長しているため、急速な成長を報告しています。各割り当ては、現在使用されているメモリブロックの「最後」になります。

38
James Lyons

PHP> = 5.3.0)では、gc_collect_cycles()を呼び出してGCパスを強制できます。

注:_zend.enable_gc_を有効にして_php.ini_を有効にするか、gc_enable()を呼び出して循環参照コレクターをアクティブにする必要があります。

19
Mo.

解決策が見つかりました。これは文字列の連結でした。私はいくつかの変数を連結することにより、行ごとに入力を生成していました(出力はCSVファイルです)。ただし、PHPは、文字列の古いコピーに使用されたメモリを解放しないため、RAM使用されていないデータを効果的に破壊します。配列ベースへの切り替えアプローチ(および出力ファイルに入力する直前にコンマで内包する)は、この動作を回避しました。

何らかの理由で-私には明らかではありません-PHPはjson_decode呼び出し中のメモリ使用量の増加を報告しました。これはjson_decode関数が問題であるという誤解を招きます。

12
DBa

PHPの内部メモリマネージャーは、関数の完了時に呼び出される可能性が高いことがわかりました。それを知って、私はループ内のコードを次のようにリファクタリングしました:

_while (condition) {
  // do
  // cool
  // stuff
}
_

_while (condition) {
  do_cool_stuff();
}

function do_cool_stuff() {
  // do
  // cool
  // stuff
}
_

[〜#〜] edit [〜#〜]

この簡単なベンチマークを実行しましたが、メモリ使用量の増加は見られませんでした。これは、リークがjson_decode()にないことを信じさせる

_for($x=0;$x<10000000;$x++)
{
  do_something_cool();
}

function do_something_cool() {
  $json = '{"a":1,"b":2,"c":3,"d":4,"e":5}';
  $result = json_decode($json);
  echo memory_get_peak_usage() . PHP_EOL;
}
_
9
Mike B

各ステートメントの後にmemory_get_peak_usage()を呼び出し、unset()できることをすべて確認してください。 foreach()で繰り返し処理する場合は、参照変数を使用して、元のコピーを作成しないようにします( foreach() )。

foreach( $x as &$y)

PHPが実際にメモリをリークしている場合、強制的なガベージコレクションは違いを生じません。

PHPメモリリークとその検出 [〜#〜] ibm [〜#〜] に良い記事があります。

6
Andy

私はちょうど同じ問題を抱えていて、可能な回避策を見つけました。

SITUATION: dbクエリからcsvファイルに書き込みました。私は常に1つの$ rowを割り当て、次のステップでそれを再割り当てしました。 $ rowを設定解除しても解決しませんでした。 (断片化を避けるために)5MBの文字列を最初に$ rowに入れても役に立ちませんでした。 $ row-sの配列を作成する(多くの行を読み込む+ 5000番目のステップごとにすべてを設定解除する)のは役に立ちませんでした。いくつか試してみました。

しかし。

ファイルを開き、100.000行を転送し(メモリ全体を食い尽くさない程度)、ファイルを閉じる別の関数を作成した後、この関数への後続の呼び出し(既存のファイルに追加)を行い、すべての関数が終了し、PHPはゴミを削除しました。

CONCLUSION:関数が終了するたびに、すべてのローカル変数が解放されます。

私が知る限り、これがルールです。ただし、「do_only_a_smaller_subset()」関数が参照によっていくつかの変数(クエリオブジェクトとファイルポインター)を取得しようとしたとき、ガベージコレクションは行われませんでした。今、私は何かを誤解しているのかもしれませんし、クエリオブジェクト(mysqli)がリークしているのかもしれません。ただし、refによって渡されたため、小さな関数の出口点の後に存在していたため、明らかにクリーンアップできませんでした。

だから、試してみる価値がある!これを見つけるために私の一日を救った。

6
dkellner

おそらくgc_collect_cycles()が問題を解決することを期待するとは限らないと言うつもりでした-おそらくファイルはもはやzvarsにマップされていないからです。しかし、ファイルをロードする前にgc_enableが呼び出されたことを確認しましたか?

PHPは、インクルードを実行するとメモリを消費するようです-ソースおよびトークン化されたファイルに必要なものよりもはるかに多く-これは同様の問題である可能性があります。しかしこれはバグです。

1つの回避策は、file_get_contentsではなく、ファイル全体を一度にメモリにマッピングするのではなく、fopen().... fgets()... fclose()を使用することです。ただし、確認するには試してみる必要があります。

HTH

C.

4
symcbean

最近、 System_Daemon同様の問題 がありました。今日、私は問題をfile_get_contents

代わりにfreadを使用してみてください。これで問題が解決するかもしれません。もしそうなら、おそらくPHPでバグレポートをする時間です。

3
kvz