現在の言語: 日本語

戻る

cow(copy on write)
メモリ解放関連

COW (Copy-On-Write:コピーオンライト)
メモリ効率を向上させるための重要な最適化メカニズムです。
PHPはデータを別の変数に代入したり
関数に値渡ししたりする際などの処理をする際、コピーをします。
しかし、データの変更がない等の場合はコピーが必要ありません。
コピーをするとメモリを使用することになるので
すぐにコピーをするのではなく
必要なときのみコピーをする仕組みです。

COWの仕組み
[代入処理]
$aの内容を$bに代入する場合
データを複製する変わりに同じメモリ領域への参照(ポインタ)を共有させます。
zvalというデータを格納している構造体の参照カウントをインクリメントします。
この時点でメモリ使用量は、ほとんど増えません。

[書き込み処理]
代入した$bが変更しようとしたときphpが検知します。
このとき初めて新しいメモリ領域に元のデータをコピーします。
そして新しいコピーに対して変更を適用します。
これにより元の$aのデータは影響を受けず、各変数が独立したデータを保持できます。


[サンプル]

copy
class test1
{
    function test1()
    {
        //$aに文字列を設定した時点で1つのデータが存在します(参照カウント:1)
        $a = "init";
        echo "(1)メモリ使用量: " . memory_get_usage() . " バイト\n";//(1)メモリ使用量: (例)429472 バイト
        //この時点で$aと$bが同じメモリ領域を参照します(参照カウント:2)
        $b = $a;
        echo "(2)メモリ使用量: " . memory_get_usage() . " バイト\n";//(2)メモリ使用量: (例)429472 バイト
        //$aおよび$bおよび$cは同じメモリを参照します(参照カウント:3)
        $c = $a;
        echo "(3)メモリ使用量: " . memory_get_usage() . " バイト\n";//(3)メモリ使用量: (例)429472 バイト
        //この時点で$b用に新しいメモリ領域が確保され、データがコピーされます
        //$aと$cは元のメモリ領域を参照したままとなります
        $b .= " add";
        echo "(4)メモリ使用量: " . memory_get_usage() . " バイト\n";//(4)メモリ使用量: (例)429512 バイト
    }
    function test2()
    {
        //[COWによるメモリ解放]
        echo "スクリプト開始時のメモリ使用量: " . memory_get_usage() . " バイト\n";
        
        // 1. 大きな文字列を作成
        $a = str_repeat('a', 1024 * 1024); // 1MBの文字列
        echo "\$a 作成後のメモリ使用量: " . memory_get_usage() . " バイト\n";
        // ここで約1MBメモリが増える
        
        // 2. COWによる代入(メモリ共有)
        $b = $a;
        // この時点では新しいメモリは確保されない(参照カウントが増えるだけ)
        echo "\$b = \$a 実行後のメモリ使用量: " . memory_get_usage() . " バイト\n";
        // メモリ使用量はほぼ変わらない
        
        // 3. COWの解除(書き込み発生)
        // $b に変更を加えることで、PHPが新しいメモリ領域に$b用のコピーを作成する
        $b[0] = 'b';
        echo "\$b に変更を加えた後のメモリ使用量: " . memory_get_usage() . " バイト\n";
        // ここで$bのために新たに約1MBメモリが確保されるため、合計で約2MB分のメモリが使われる
        
        // 4. COWによるメモリ解放の瞬間
        // ここで、元の変数 $a を NULL に設定して破棄する
        $a = null;
        echo "\$a を NULL に設定した後のメモリ使用量: " . memory_get_usage() . " バイト\n";
        // $a が参照していた元のメモリ領域の参照カウントが 0 になるため、
        // ガベージコレクタによってそのメモリ領域が解放される。
        // メモリ使用量が約1MB分減少し、最終的に$bが使っている約1MB分だけになる。
        
        // 5. スクリプト終了
        unset($b);
        echo "\$b を unset した後のメモリ使用量: " . memory_get_usage() . " バイト\n";
        // $b のメモリも解放され、開始時の値に近くなる
    }
}
echo "<pre>";

$cls1 = new test1();
$cls1->test1();
$cls1->test2();
echo "</pre>";
//[実行例]
/*
スクリプト開始時のメモリ使用量: 428008 バイト
$a 作成後のメモリ使用量: 1480680 バイト
$b = $a 実行後のメモリ使用量: 1480680 バイト
$b に変更を加えた後のメモリ使用量: 2533352 バイト
$a を NULL に設定した後のメモリ使用量: 1480680 バイト
$b を unset した後のメモリ使用量: 428008 バイト
*/
copy
class test1
{
	function test1()
	{
		//At the time a string is set to $a, one piece of data exists (reference count: 1)
		$a = "init";
		echo "(1) Memory usage: " . memory_get_usage() . " bytes\n"; //(1) Memory usage: (Example) 429472 bytes
		//At this point, $a and $b reference the same memory area (reference count: 2)
		$b = $a;
		echo "(2) Memory usage: " . memory_get_usage() . " bytes\n"; //(2) Memory usage: (Example) 429472 bytes
		//$a, $b, and $c reference the same memory area (reference count: 3)
		$c = $a;
		echo "(3) Memory usage: " . memory_get_usage() . " bytes\n"; //(3) Memory usage: (Example) 429472 bytes
		// At this point, a new memory area is allocated for $b and the data is copied.
		// $a and $c continue to reference the original memory area.
		$b .= " add";
		echo "(4) Memory usage: " . memory_get_usage() . " bytes\n"; // (4) Memory usage: (Example) 429512 bytes
	}
	function test2()
	{
		// [Freeing memory using COW]
		echo "Memory usage at script start: " . memory_get_usage() . " bytes\n";
		
		// 1. Create a large string
		$a = str_repeat('a', 1024 * 1024); // 1MB string
		echo "Memory usage after $a creation: " . memory_get_usage() . " bytes\n";
		// This adds approximately 1MB of memory.
		
		// 2. Assignment using COW (memory sharing)
		$b = $a;
		// No new memory is allocated at this point (only the reference count increases).
		
		echo "Memory usage after executing \$b = \$a: " . memory_get_usage() . " bytes\n";
		// Memory usage remains almost unchanged.
		
		/ 3. Cancel COW (write occurs)
		/ By making changes to $b, PHP creates a copy of $b in a new memory area.
		$b[0] = 'b';
		echo "Memory usage after making changes to \$b: " . memory_get_usage() . " bytes\n";
		/ At this point, approximately 1MB of new memory is allocated for $b, for a total of approximately 2MB of memory used.
		
		/ 4. The moment memory is freed by COW.
		/ Here, the original variable $a is set to NULL to discard it.
		$a = null;
		echo "Memory usage after setting \$a to NULL: " . memory_get_usage() . " bytes\n";
		/ $a The reference count of the original memory area referenced by becomes 0,
		// so the garbage collector frees that memory area.
		// Memory usage decreases by about 1 MB, and finally only about 1 MB is used by $b.
		
		// 5. Script End
		unset($b);
		echo "\Memory usage after unsetting $b: " . memory_get_usage() . " bytes\n";
		// The memory for $b is also freed, returning it to its starting value.
		}
}
echo "<pre>";

$cls1 = new test1();
$cls1->test1();
$cls1->test2();
echo "</pre>";
//[Execution Example]
/*
Memory usage at script start: 428008 bytes
Memory usage after creating $a: 1480680 bytes
Memory usage after executing $b = $a: 1480680 bytes
Memory usage after modifying $b: 2533352 bytes
Memory usage after setting $a to NULL: 1,480,680 bytes
Memory usage after unsetting $b: 428,008 bytes
*/



$a = "init";
文字列にデータを設定した時点で1つのデータが存在します。(参照カウント:1)
$b = $a;
この時点で$aと$bが同じメモリ領域を参照します(参照カウント:2)
$c = $a;
$aおよび$bおよび$cは同じメモリを参照します(参照カウント:3)
$b .= " add";(参照が切断され参照カウント:2)
この時点で$b用に新しいメモリ領域が確保され、データがコピーされます。
$aと$cは元のメモリ領域を参照したままとなります。

$a = null;
$aの\変数が破棄され参照カウンタが減ります。
そのためガベージコレクションにより、$aのメモリ領域が解放されます。
unset($b);
$bのメモリも解放されます。

copy
class test2
{
	function test()
	{
		$a = 100;
		$this->test2($a);
		$a = null;
		unset($a);
		
		$a =100;
	}
	private function test2(int $num):int
	{
		return $num++;
	}
}
echo "<pre>";

$cls2 = new test2();
$cls2->test();
echo "</pre>";
copy
class test2
{
	function test()
	{
		$a = 100;
		$this->test2($a);
		$a = null;
		unset($a);
		
		$a =100;
	}
	private function test2(int $num):int
	{
		return $num++;
	}
}
echo "<pre>";

$cls2 = new test2();
$cls2->test();
echo "</pre>";
$a = 100;
変数$aを作成して値100を格納します。
$aが参照するメモリ領域の参照カウントは1になります。
test2($a);
test関数を実行します。
$aの値が引数を通りして$numに値渡しされます。
引数の値渡しはデフォルトでCOW(copu on write)の対象となります。
関数内で$numは作成されますが
$aと共通されます。
この時点で参照カウントが2になります。
$numも$aも100の値となります。

return $num++;
$numがインクリメントされます。
インクリメントするということは値が変更するということなので
変更する前に$num用の新しいメモリ領域にデータ100をコピーします。
$numは変更したため参照が切断されます。
そのため参照カウントは1になります。
そして新しい(インクリメントされた)データは101となります。

returnで値を返した後、$numは関数のスコープを抜けて破棄されます。
$numの参照が切断されるため参照カウントが0となり
データ101は即時解放されます。

関数を抜けたら元の場所に戻るので
$aはそのまま残っているため、参照カウントが1となります。
そのため、参照カウントはゼロになりません。

そのため参照カウントを0にするには
$a = null;
もしくは
unset($a);
このように$aにnullを設定するか、unset関数を実行しないと
参照カウントが0にならないため即時解放処理は実行されません。



戻る

著作権情報
ホームページおよプリ等に掲載されている情報等については、いかなる保障もいたしません。
ホームページおよびアプリ等を通じて入手したいかなる情報も複製、販売、出版または使用させたり、
または公開したりすることはできません。
当方は、ホームページおよびアプリ等を利用したいかなる理由によっての障害等が発生しても、
その結果ホームページおよびアプリ等を利用された本人または他の第三者が被った損害について
一切の責任を負わないものとします。