PHPのメモリについて(Part2)

どうもこんにちは。BriswellのTuanと申します。
今日また、PHPのメモリについて続きたいと思います。
前回の内容を参考したい方は下記のリンクを参考してください。

PHPのメモリについて(Part1) - Briswell Tech Blog

PHPは自動的にメモリ管理できると言うことを分かりましたが、何を管理しているのでしょうか?

まず、値代入参照代入を簡単に説明します。
PHPには基本的にオブジェクト以外各変数は値代入です。
例えば下記のコード確認しましょう:

<?php 
$a = 1; 
$b = $a;    
$a++;    
var_dump($a, $b);  
    
$x = (object)['value' => 3]; 
$y = $x;    
$x->value = 10;  
var_dump($x); 
var_dump($y);

実行結果

int(2)

int(1)

object(stdClass)#1 (1) { ["value"]=> int(10) }

object(stdClass)#1 (1) { ["value"]=> int(10) }

$a, $bの値は簡単にわかるでしょうか!
しかし、$x$yはどう思ういますか? $x, $yはオブジェクトですから値代入ではなく、参照代入ので、
個別のオブジェクトですが、実際は同じメモリ位置に参照しますので、$xを更新すると、$yにも反映されます。

関数の引数もデフォルトは値渡しです。

<?php 
function fnByVal($v) { 
        $v = "test";  
}   

// 参照渡しを指定
function fnByRef(&$v) 
{   
        $v = "test";  
}   
$y = 'Briswell';    
fnByVal($y); var_dump($y); 
fnByRef($y); var_dump($y); 

実行結果

string(8) "Briswell"
string(4) "test"

上記のコードを実行して、fnByValを呼んだ後に$yの値はそのまま「Briswell」でしょうか! でもfnByRefの場合、$yの値更新されました。

ちょっと面白いと思いますが、下記のコードを実行したら、結果は想定と同じでしょうか!自分で試して、是非コメントで説明してください。

<?php 
function fnByVal_($obj) {  
        $obj->value = "test";  
}   
$x = (object)['value' => 1]; 
fnByVal_($x); var_dump($x);    

PHPは必要であれば、自動的にメモリをアロケート又は解放します。
ただし、「必要であれば」はいつでしょうか? これから、色々を試しながら確認しましょう。

※注意:以降の例は基本的に検証の想定通りですが、PHPバーションによって、メモリ管理方違う部分があるので結果違います。ご注意してください。

例1:

<?php 
    $mem = memory_get_usage();    
    $a = str_repeat("1", 1000); // ①       
    echo ">> used mem: ". (memory_get_usage() - $mem) ."\n"; 
    $b = $a;                    // ②
    $c = $b;                    // ③ 
    echo ">> used mem: ". (memory_get_usage() - $mem) ."\n"; 
        
    $a = $a . '1';               // ④ 
    echo ">> used mem: ". (memory_get_usage() - $mem) ."\n"; 
        
    unset($b);                   // ⑤ 
    echo ">> used mem: ". (memory_get_usage() - $mem) ."\n"; 
    unset($c);                   // ⑥ 
        
echo ">> used mem: ". (memory_get_usage() - $mem) ."\n"; 

実行結果

used mem: 1280
used mem: 1312
used mem: 2592
used mem: 2592
used mem: 1312

ソース見るだけ分かりずらいと思いますが、下図を見てください。

f:id:tuanda:20200831183105p:plain

f:id:tuanda:20200831183143p:plain

配列も同じ管理方です。
例2:

<?php 
$mem = memory_get_usage();    
$a = array_fill(0, 1000, '1'); 
echo ">> used mem: ". (memory_get_usage() - $mem) ."\n"; 
$b = $a;    
$c = $b;    
echo ">> used mem: ". (memory_get_usage() - $mem) ."\n"; 
$a[0] = '2';   
echo ">> used mem: ". (memory_get_usage() - $mem) ."\n"; 
$a[] = '1'; 
echo count($a)."\n";  
echo count($b)."\n";  
echo count($c)."\n";  
echo ">> used mem: ". (memory_get_usage() - $mem) ."\n"; 
    
>unset($b);   
echo ">> used mem: ". (memory_get_usage() - $mem) ."\n"; 
unset($c);   
    
>echo ">> used mem: ". (memory_get_usage() - $mem) ."\n";   

実行結果

used mem: 36920
used mem: 36952
used mem: 73872
1001
1000
1000
used mem: 73872
used mem: 73872
used mem: 36952

例3:

<?php 
$mem = memory_get_usage();    
$a = array_fill(0, 1000, (object) array('x' => '1'));  
echo ">> used mem: ". (memory_get_usage() - $mem) ."\n"; 
$b = $a;    
$c = $b;    
echo ">> used mem: ". (memory_get_usage() - $mem) ."\n"; 
$a[0]->x = '2';    
echo ">> used mem: ". (memory_get_usage() - $mem) ."\n"; 
$a[] = '1'; 
echo count($a)."\n";  
echo count($b)."\n";  
echo count($c)."\n";  
echo ">> used mem: ". (memory_get_usage() - $mem) ."\n"; 
    
>unset($b);   
echo ">> used mem: ". (memory_get_usage() - $mem) ."\n"; 
unset($c);   
    
>echo ">> used mem: ". (memory_get_usage() - $mem) ."\n";   

実行結果

used mem: 36960
used mem: 36992
used mem: 74288
1001
1000
1000
used mem: 74288
used mem: 74288
used mem: 37368

試した結果上で、PHPには同じメモリエリアに複数変数が参照できますが、
それぞれの変数がいずれか更新したい場合は別のメモリエリアをアロケートします。
これは大切だと思います。

*データベースから数万件のデータ抽出する時に、注意しないと、「Fatal error: Out of memory」発生可能性が高いです。

例えばデータ処理はほとんどForeachというループ命令を使ってますが、下記の2つバーションのForeach確認しましょう。

一般的バーション

<?php     
$mem = memory_get_usage();        
$a = [  array_fill(0, 1000, '1'),      
        array_fill(0, 1000, '2'),     
        array_fill(0, 1000, '3'),     
        array_fill(0, 1000, '4')];        
echo ">> used mem: ". (memory_get_usage() - $mem) ."\n";     
foreach ( $a as $key => $item) {        
     $a[$key][0] = '9';       
    echo ">> used mem: ". (memory_get_usage() - $mem) ."\n";     
}       

実行結果

used mem: 148056
used mem: 185384
used mem: 222304
used mem: 259224
used mem: 296144

参照バーション

<?php 
$mem = memory_get_usage();    
$a = [  array_fill(0, 1000, '1'),  
        array_fill(0, 1000, '2'), 
        array_fill(0, 1000, '3'), 
        array_fill(0, 1000, '4')];    
echo ">> used mem: ". (memory_get_usage() - $mem) ."\n"; 
foreach ( $a as <b><span style="color: #0000cc">&</span></b>$item) { 
    $item[0] = '9';        
    echo ">> used mem: ". (memory_get_usage() - $mem) ."\n"; 
}   

実行結果

used mem: 148056
used mem: 148152
used mem: 148184
used mem: 148216
used mem: 148248

結果見ると、どちら効率かすぐ分かりますね。でも、一般的皆あまり参照のバーション使ってないと思います。

PHPは自動的にメモリを解放できますが、必要であれば、自分達「unset」で解放できます。
PHPソースには配列を多分一番よく使うデータ型ですが、参照代入ではなくて、値代入ので、ご注意してください。
意外の増やすメモリのケースが多いと思います。

ではPHPのメモリについてここまで終わります。
みなさん是非試して確認して、実際のプロジェクトに適用すれば、良かっただと思います。