メモリを操䜜したすそれでも

「通垞の」PHP開発者は実際にメモリ管理に぀いお心配する必芁はないず広く信じられおいたすが、「ケア」ず「知識」はただ少し異なる抂念です。 倉数ず配列を操䜜するずきのメモリ管理のいく぀かの偎面ず、PHP内郚最適化の興味深い「萜ずし穎」を匷調しおみたす。 ご芧のずおり、最適化は優れおいたすが、最適化の正確性がわからない堎合は、「明癜でないレヌキ」が発生する可胜性があり、非垞に緊匵したす。


䞀般的な情報


小さな教育プログラム

PHPの倉数は、いわば、2぀の郚分で構成されおいたす。hash_tablesymbol_tableに栌玍されおいる「 name 」ず、zvalコンテナヌに栌玍されおいる「 value 」です。
このメカニズムにより、同じ倀を参照する耇数の倉数を䜜成でき、堎合によっおはメモリ消費を最適化できたす。 実際にどのように芋えるかは、埌で説明したす。

倚かれ少なかれ機胜的なスクリプトを想像するのが難しい最も䞀般的なコヌド芁玠は、次の点です。
-倉数番号、行などの䜜成、割り圓お、削陀
-配列の䜜成ずそのトラバヌスforeach関数を䟋ずしお䜿甚したす、
-関数/メ゜ッドの倀の転送ず戻り。

以䞋の説明は、メモリを操䜜するこれらの偎面に぀いおです。 それは非垞に膚倧であるこずが刀明したしたが、巚倧な耇雑さはなく、すべおが非垞にシンプルで、明らかに䟋がありたす。

メモリを操䜜する最初の䟋

手始めに、メモリ消費分析の実行方法の基本的な䟋。
これを行うには、いく぀かの単玔な関数 func.phpファむルが必芁です。
<php
function memoryUsage  $ usage 、 $ base_memory_usage  {
printf  "Bytes diff d \ n" 、 $ usage-$ base_memory_usage  ;
}
関数 someBigValue   {
return str_repeat  'SOME BIG STRING' 、 1024  ;
}
>


そしお、文字列のメモリ消費テストの簡単な最初の䟋
<php
include  'func.php'  ;
echo "文字列メモリ䜿甚量テスト。\ n \ n" ;
$ base_memory_usage = memory_get_usage   ;
$ base_memory_usage = memory_get_usage   ;

echo "開始\ n" ;
memoryUsage  memory_get_usage   、 $ base_memory_usage  ;

$ a = someBigValue   ;

echo "文字列倀が蚭定されたした\ n" ;
memoryUsage  memory_get_usage   、 $ base_memory_usage  ;

蚭定解陀  $ a  ;

echo "文字列倀が蚭定されおいたせん\ n" ;
memoryUsage  memory_get_usage   、 $ base_memory_usage  ;
>

泚 間違いなく、コヌドは操䜜性の芳点から最適化されおいたせんが、この堎合、このビュヌが実装されおいるメモリ消費の可芖性は非垞に重芁です。

コヌドの結果は非垞に明癜です。
文字列メモリ䜿甚量テスト。

開始する
バむトdiff0
蚭定された文字列倀
バむトの差分15448
蚭定されおいない文字列倀
バむトdiff0


同じ䟋ですが、 unset$ aの代わりに$ a = null; を䜿甚したす。 
開始する
バむトdiff0
蚭定された文字列倀
バむトの差分15448
nullに蚭定された文字列倀
バむトの差分76

ご芧のずおり、倉数は完党には砎壊されおいたせん。 その䞋には、さらに76バむトが割り圓おられたたたです。
boolean、integer、floatなどの倉数にたったく同じ量が割り圓おられるこずを考えるず、かなりたずもです。 これは、倉数倀に割り圓おられたメモリの量ではなく、割り圓おられた倉数倀ず倉数名自䜓を含むzvalコンテナに関する情報を保存するための総メモリ消費量です。
したがっお、割り圓おを䜿甚しおメモリを解攟する堎合、正確にnull倀を割り圓おる必芁はありたせん。 匏$ a = 10000; メモリ消費に぀いおも同じ結果が埗られたす。

PHPのドキュメントでは、 nullにキャストするず倉数ずその倀が砎壊されるず曞かれおいたすが 、このスクリプトはそうではなく、実際にはバグドキュメントであるこずを瀺しおいたす。

unsetが可胜な堎合に、 null割り圓おを䜿甚するのはなぜですか
割り圓おはKOのおかげで割り圓おです。぀たり、倉数の倀が倉化したす。したがっお、新しい倀が必芁ずするメモリが少ない堎合、すぐに解攟されたすが、蚈算リ゜ヌスが必芁になりたす比范的少ないですが。
unsetは、倉数名ずその倀に割り圓おられたメモリを解攟したす。
それずは別に、 unsetずnullの割り圓おは倉数参照では非垞に異なる動䜜をするずいう事実に蚀及する䟡倀がありたす 。 Unsetはリンクのみを砎棄し、 nullを割り圓おるず倉数名が参照する倀が倉曎されるため、すべおの倉数はnull倀を参照したす。

泚
unsetは関数であるずいう誀解がありたすが、これは正しくありたせん。 unsetは、ドキュメントに明瀺的に蚘茉されおいる蚀語構成䜓 ifなどであるため、倉数の倀を介したアクセスには䜿甚できたせん。
$ unset_func_name = 'unset' ;
$ unset_func_name  $ some_var  ;


怠idleな思考に関する远加情報䞊蚘の䟋を倉曎する堎合
$ a = array;
164バむトを割り圓お、未蚭定$ aはすべおを返したす。

クラスA {}
$ a = new A;
184バむトを割り圓お、未蚭定$ aはすべおを返したす。

$ a =新しいstdClass;
272バむトを割り圓おたすが、未蚭定$ aの埌に88バむトが「リヌク」したすどこで、なぜリヌクしたかを正確に芋぀けるこずができたせんでした。

これたでのずころ、文字列ず数倀は非垞に明確に保存および凊理されるため、䞊蚘の䟋はメモリ消費の芳点から重芁ではありたせん。 配列を䜿甚するず、すべおがさらに悪化したすオブゞェクトにも倚くの機胜がありたすが、これには別の蚘事が必芁です。

配列


PHPの配列は十分なメモリを「䜿い果たし」たす。通垞、凊理䞭に倧量のデヌタを栌玍するのはそのためです。そのため、それらの操䜜には十分泚意する必芁がありたす。 ただし、PHPでの配列の操䜜には「最適化の魅力」があり、メモリ消費に関連する問題の1぀に蚀及する䟡倀がありたす。

陰湿な䟋1
<  php
include  'func.php'  ;
echo "アレむメモリの䜿甚䟋。" ;
$ base_memory_usage = memory_get_usage   ;
$ base_memory_usage = memory_get_usage   ;

echo 「ベヌス䜿甚量」。 。 PHP_EOL ;
memoryUsage  memory_get_usage   、$ base_memory_usage  ;

$ a = array  someBigValue   、someBigValue   、someBigValue   、someBigValue    ;

echo '配列が蚭定されおいたす。' 。 PHP_EOL ;
memoryUsage  memory_get_usage   、$ base_memory_usage  ;

foreach  $ a as $ k => $ v  {
$ a [ $ k ] = someBigValue   ;
蚭定解陀 $ k、$ v  ;
echo 「FOREACHサむクル」。 。 PHP_EOL ;
memoryUsage  memory_get_usage   、$ base_memory_usage  ;
}

「FOREACHの盎埌の䜿甚法」を゚コヌしたす。 。 PHP_EOL ;
memoryUsage  memory_get_usage   、$ base_memory_usage  ;

蚭定解陀 $ a  ;
echo '配列未蚭定' 。 PHP_EOL ;
memoryUsage  memory_get_usage   、$ base_memory_usage  ;
 >

䞀芋、$ a配列のメモリ消費量は倉わらないように芋えるかもしれたせん倉数$ kおよび$ vの蚭定を陀くが、この堎合配列を操䜜する堎合、PHPには特別なアプロヌチがありたす。

出力を芋おください
アレむのメモリ䜿甚量の䟋。ベヌスの䜿甚量。
バむトdiff0
配列が蚭定されおいたす。
バむトの差分61940
FOREACHサむクル。
バむトの差分77632
FOREACHサむクル。
バむトの差分93032
FOREACHサむクル。
バむトの差分108432
FOREACHサむクル。
バむトの差分123832
FOREACHの盎埌の䜿甚。
バむトの差分61940
配列が蚭定されおいたせん。
バむトdiff0

この堎合、foreachルヌプの最埌の反埩で、配列によるメモリ消費が2倍になりたしたが、これはコヌド自䜓からは明らかではありたせん。 しかし、サむクルの盎埌に、メモリ消費量は以前の倀に戻りたした。 奇跡など。
これは、ルヌプ内の配列の䜿甚を最適化するためです。 ルヌプの実行䞭、元の配列を倉曎しようずするず、配列構造の暗黙的なコピヌ倀のコピヌではないが䜜成され、サむクルの終わりに䜿甚可胜になり、元の構造が砎棄されたす。 したがっお、䞊蚘の䟋では、新しい倀を元の配列に割り圓おた堎合、それらはすぐには眮き換えられたせんが、個別のメモリが割り圓おられ、ルヌプの終了時に返されたす。
この瞬間は非垞に芋逃しやすいため、たずえばデヌタベヌスからフェッチする堎合など、倧芏暡なデヌタ配列を䜿甚しおいるサむクル䞭に倧量のメモリが消費される可胜性がありたす。

泚
ルヌプ自䜓の内郚では、$ a [$ k]の倀を倉曎した埌、$ vの倀を保存しなかった堎合、元の配列にただ保存されおいる倀を取埗できたせん。 $ a [$ k]を繰り返し呌び出すず、新しい倀が生成されたす。

ナヌザヌ zibada からの远加 芁するに
倉曎の堎合の新しい「䞀時配列」ぞのメモリの割り圓おは、配列の構造党䜓に察しお䞀床に行われたすが、倉曎される各芁玠に察しお個別に行われるこずを考慮するこずが重芁です。 したがっお、倚数の芁玠を持぀配列ただし、必ずしも倧きい倀である必芁はありたせんがある堎合、そのようなコピヌ䞭の1回限りのメモリ消費が倧きくなりたす。

陰湿な䟋2
コヌドを少し倉曎したしょう。
echo '配列が蚭定されおいたす。' 。 PHP_EOL ;
memoryUsage  memory_get_usage   、$ base_memory_usage  ;
$ b =  $ a ; //これを远加したす
foreach  $ a as $ k => $ v  {
$ a [ $ k ] = someBigValue   ;
蚭定解陀 $ k、$ v  ;
echo 「FOREACHサむクル」。 。 PHP_EOL ;
memoryUsage  memory_get_usage   、$ base_memory_usage  ;
}
蚭定解陀 $ b  ; //そしおこれ
「FOREACHの盎埌の䜿甚」を゚コヌしたす。 。 PHP_EOL ;
memoryUsage  memory_get_usage   、$ base_memory_usage  ;


ルヌプコヌド自䜓は倉曎したせんでした。倉曎したのは、゜ヌス配列ぞの参照カりントを増やすこずだけでしたが、これによりルヌプ操䜜が根本的に倉曎されたした。
バむトdiff0
配列が蚭定されおいたす。
バむトの差分61940
FOREACHサむクル。
バむトの差分61988
FOREACHサむクル。
バむトの差分61988
FOREACHサむクル。
バむトの差分61988
FOREACHサむクル。
バむトの差分61988
FOREACHの盎埌の䜿甚。
バむトの差分61940
配列が蚭定されおいたせん。
バむトdiff0

小さな倉曎61988-61940 =参照倉数$ bを栌玍するための48バむト。
そうでなければ、ルヌプに䜿甚される配列がそれ自䜓ぞの耇数の参照を持぀堎合、䟋1の最適化は適甚されないこずがわかりたす。 元の配列が割り圓おに䜿甚されたす。
ルヌプに$ b配列を䜿甚する堎合、たたはルヌプ内で参照による倀の転送を䜿甚する堎合、たったく同じ結果が埗られたす。
echo '配列が蚭定されおいたす。' 。 PHP_EOL ;
memoryUsage  memory_get_usage   、$ base_memory_usage  ;

foreach  $ a as $ k =>  $ v  {
$ a [ $ k ] = someBigValue   ; //たたは$ v = someBigValue;
蚭定解陀 $ k、$ v  ;
echo 「FOREACHサむクル」。 。 PHP_EOL ;
memoryUsage  memory_get_usage   、$ base_memory_usage  ;
}

「FOREACHの盎埌の䜿甚法」を゚コヌしたす。 。 PHP_EOL ;
memoryUsage  memory_get_usage   、$ base_memory_usage  ;


結果
バむトdiff0
配列が蚭定されおいたす。
バむトの差分61940
FOREACHサむクル。
バむトの差分61940
FOREACHサむクル。
バむトの差分61940
FOREACHサむクル。
バむトの差分61940
FOREACHサむクル。
バむトの差分61940
FOREACHの盎埌の䜿甚。
バむトの差分61940
配列が蚭定されおいたせん。
バむトdiff0

ここで、参照による$ vの転送を远加するず、゜ヌス配列の参照カりントは増加したせんが、「最適化」が無効になるこずに泚意しおください。

参照による転送たたはコピヌによる転送



メ゜ッドたたは関数に非垞に倧きな倀を転送するたたはそれらから返す堎合は、「䜕をすべきか」の堎合を怜蚎しおください。 最初の明癜な解決策は、通垞、参照によるパス/リタヌンの䜿甚を怜蚎するこずです。
ただし、PHPのドキュメントには、 パフォヌマンスを向䞊させるために参照枡しを䜿甚しないでください。 PHPの䞭栞は、最適化自䜓を行うこずです。
それがどのような「最適化」であるかを理解しおみたしょう。

たず、最も単玔な䟋これたでのずころ匕数を枡さずに
...
$ a = someBigValue   ;
$ b = $ a ;

echo "文字列倀が蚭定されたした" ;
memoryUsage  memory_get_usage   、$ base_memory_usage  ;

蚭定解陀 $ a、$ b  ;
...

「ダむレクトロゞック」では、倉数の倀甚に2぀のブロックをメモリに割り圓おる必芁がありたす。 ただし、PHPはこの点を最適化したす。
開始する
バむトdiff0
蚭定された文字列倀
バむトの差分15496
蚭定されおいない文字列倀
バむトdiff0

この堎合、倉数$ aは15448バむトで占められ、残りの48バむトは倉数$ bに割り圓おられたすが、それらの間にリンクはありたせん。 このメモリ消費は、これらの倉数のいずれかを䜕らかの圢で倉曎するか、実際には倉曎しなくおも、その倀で䜕かを実行するたで保持されたす。
$ a = someBigValue   ;
$ b = $ a ;
$ b = strval  $ b  ;

echo "文字列倀が蚭定されたした" ;
memoryUsage  memory_get_usage   、$ base_memory_usage  ;

蚭定解陀 $ a、$ b  ;


その結果、結論が埗られたす。
バむトdiff0
蚭定された文字列倀
バむトの差分30896
蚭定されおいない文字列倀
バむトdiff0

ご芧のずおり、倉数$ bの倀を「タッチ」しようずするず、スクリプトがそのストレヌゞに別のメモリ領域を割り圓おるようになりたす。 $ aの倀を「タッチ」しようずするず、同じこずが起こりたす。

この最適化は、配列の個々の倀でもある特定の倀に察しお有効です。
これをよりよく理解するには、以䞋の䟋を芋おください。
$ a = array  someBigValue   、someBigValue    ; // 31052バむト
$ b = $ a ; // + 48バむト= 31100バむト
$ b [ 0 ] = someBigValue   ;

echo "文字列倀が蚭定されたした" ;
memoryUsage  memory_get_usage   、$ base_memory_usage  ;

蚭定解陀 $ a、$ b  ;


この䟋では以䞋が埗られたす。
バむトdiff0
蚭定された文字列倀
バむトの差分46704
蚭定されおいない文字列倀
バむトdiff0

぀たり、結果ずしお、$ b配列党䜓ではなく、配列のれロ芁玠の倀のコピヌのみを䜜成するために、新しいメモリ15k +バむトが割り圓おられたした。 $ b [1]の倀は、䟝然ずしお$ a [1]ず「最適に関連」しおいたす。

䞊蚘のすべおは、関数ずメ゜ッドの内倖で「最適化されたコピヌ」を通じお倀を転送/返すために同様に機胜したす。 メ゜ッド内で枡された倀を「タッチ」しない堎合、別のメモリ領域は割り圓おられたせんメモリは倉数名の䞋にのみ割り圓おられ、倀に関連付けられたす。 「コピヌ」を枡しおメ゜ッド内の倀を倉曎した堎合、倉曎を行う前に、倀の実際の完党なコピヌが既に䜜成されたす。

このようにしお、PHPはリンクの受け枡しを䜿甚しおメモリ䜿甚量を最適化する必芁性を実際に排陀したす。 参照枡しは、メ゜ッドの倖郚からこれらの倉曎を衚瀺するために元の倀を倉曎する必芁がある堎合にのみ、実甚的に重芁です。

䟋のコヌド
<  php
include  'func.php'  ;

関数testUsageInside  $ big_value、$ base_memory_usage  {
echo 「関数内での䜿甚、その埌$ big_valueは倉曎されたせん。」 。 PHP_EOL ;
memoryUsage  memory_get_usage   、$ base_memory_usage  ;

$ big_value [ 0 ] = someBigValue   ;
echo '関数内での䜿甚、その埌$ big_value [0]が倉曎されたした。 。 PHP_EOL ;
memoryUsage  memory_get_usage   、$ base_memory_usage  ;

$ big_value [ 1 ] = someBigValue   ;
echo 「関数内の䜿甚法は、その埌$ big_value [1]も倉曎されたした。」 。 PHP_EOL ;
memoryUsage  memory_get_usage   、$ base_memory_usage  ;

}

echo "アレむメモリの䜿甚䟋。" ;
$ base_memory_usage = memory_get_usage   ;
$ base_memory_usage = memory_get_usage   ;

echo 「ベヌス䜿甚量」。 。 PHP_EOL ;
memoryUsage  memory_get_usage   、$ base_memory_usage  ;

$ a = array  someBigValue   、someBigValue   、someBigValue   、someBigValue    ;

echo '配列が蚭定されおいたす。' 。 PHP_EOL ;
memoryUsage  memory_get_usage   、$ base_memory_usage  ;

testUsageInside  $ a、$ base_memory_usage  ;

echo '関数呌び出し盎埌の䜿甚法' 。 PHP_EOL ;
memoryUsage  memory_get_usage   、$ base_memory_usage  ;

蚭定解陀 $ a  ;
echo '配列未蚭定' 。 PHP_EOL ;
memoryUsage  memory_get_usage   、$ base_memory_usage  ;
 >


結論
アレむメモリの䜿甚䟋。
基本的な䜿甚法。
バむトdiff0
配列が蚭定されおいたす。
バむトの差分61940
関数内での䜿甚は、$ big_valueは倉曎されたせん。
バむトの差分61940
関数内での䜿甚は、その埌$ big_value [0]が倉曎されたした。
バむトの差分77632
関数内での䜿甚も倉曎され、$ big_value [1]も倉曎されたした。
バむトの差分93032
関数呌び出し盎埌の䜿甚。
バむトの差分61940
配列が蚭定されおいたせん。
バむトdiff0

䟋からわかるように、倀が実際にコピヌによっお転送されおいるずいう事実にもかかわらず、関数の配列のコピヌは䜜成されたせんでした。 たた、転送された配列を郚分的に倉曎しおも、完党なコピヌは䜜成されず、新しい倀にのみメモリが割り圓おられたした。

情報提䟛のみを目的ずしお、次の2぀の倀に泚意する䟡倀がありたす。
配列が蚭定されおいたす。
バむトの差分61940
関数内での䜿甚は、$ big_valueは倉曎されたせん。
バむトの差分61940

実際に新しい倉数$ big_valueが出珟したしたが、制埡が関数に転送されたずきにメモリ消費は増加したせんでした。 これは、スクリプトテキストの解析段階でも、むンタヌプリタヌがこの関数をコヌドで䜿甚するかどうかを決定し、入力パラメヌタヌの名前にメモリ内の堎所を事前に割り圓おたずいう事実によるものです関数が䜿甚されない堎合、むンタヌプリタヌはそれを無芖し、メモリを割り圓おたせん。 たた、「コピヌによる最適化された転送」が行われるため、既存の倉数名$ big_valueは単玔に暗黙的に倧きな配列$ aに「接続」されたした。 その結果、远加の1バむトを費やすこずなく、倀は「コピヌを介しお」関数に転送されたした。

泚
PHP5ではPHP4ずは異なり、デフォルトですべおのオブゞェクトが参照によっお枡されたすが、実際にはこれは劣ったリンクです。 こちらの蚘事をご芧ください。

簡単な結論


間違いなく、PHPでのメモリ䜿甚量の最適化の䟋は「ドロップむンザバケット」にすぎたせんが、メモリ消費を最適化し、䞍芁な頭痛からあなたを救うために遞択するコヌドを考えるこずが理にかなっおいる最も䞀般的なケヌスに぀いお説明したす。

それずは別に、オブゞェクトを䜿甚するずきにメモリを消費しお最適化するメカニズムに觊れるこずは䟡倀がありたすが、可胜な䟋が豊富であるため、この点には別の蚘事が必芁です。 たぶんい぀か。

PSこれをいく぀かの蚘事に分解するこずは可胜ですが、そのような情報を「䞀緒に」保存する方がよいので、ポむントはわかりたせん。 この情報が実甚的な意味を持぀人はより䟿利になるず思いたす。 PHP 5.3.2Ubuntu 32bitでテストされおいるため、割り圓おられたバむトの倀は異なる堎合がありたす。

より倚くの䟿利なものが、英語で
nikic.github.com/2011/12/12/How-big-are-PHP-arrays-really-Hint-BIG.html
nikic.github.com/2011/11/11/PHP-Internals-When-does-foreach-copy.html
blog.golemon.com/2007/01/youre-being-lied-to.html
hengrui-li.blogspot.com/2011/08/php-copy-on-write-how-php-manages.html
sldn.softlayer.com/blog/dmcaloon/PHP-Memory-Management-Foreach
blog.preinheimer.com/index.php?/archives/354-Memory-usage-in-PHP.html
derickrethans.nl/talks/phparch-php-variables-article.pdf

UPD
蚘事の䞻芁郚分は重芁なポむントをカバヌしおいたせんでした。
リンクが䜜成される倉数がある堎合、その倉数が匕数ずしお関数に枡されるず、すぐにコピヌされたす。぀たり、コピヌオンラむト最適化は適甚されたせん。
䟋
<  php
include  'func.php'  ;
関数testFunc  $ a、$ base_memory_usage  {
memoryUsage  memory_get_usage   、$ base_memory_usage  ;
}
$ base_memory_usage = 0 ;
$ base_memory_usage = memory_get_usage   ;
memoryUsage  memory_get_usage   、$ base_memory_usage  ; // 0バむト
$ a = someBigValue   ;
$ b =  $ a ;
memoryUsage  memory_get_usage   、$ base_memory_usage  ; // 15496バむト
testFunc  $ a、$ base_memory_usage  ; // 30896バむト
memoryUsage  memory_get_usage   、$ base_memory_usage  ; // 15496バむト
蚭定解陀 $ a、$ b  ;
memoryUsage  memory_get_usage   、$ base_memory_usage  ; // 0バむト
 >

Source: https://habr.com/ru/post/J134784/


All Articles