PHPの2次元配列のメモリ使用量

今日はPHPの2次元配列がどのくらいメモリを使うかの巻。

こういう20列あるcsvが20000行あったとして
csv

0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19
・
・
・
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19


このcsvを2次元配列に展開するような状況を想定したコードを実行すると
(下記は便宜上csvと同等のデータを同時に作ってます)

<?php
// filename: array_mem.php 
$list = [];
$csv = '';
$num = 20000;
$array_length = 20;
foreach(range(0, $num-1) as $idx=>$current ){
	$item = range(0,$array_length-1);
	$list[] = $item;
	$csv .= implode(',', $item).PHP_EOL;
}

$mem = number_format(memory_get_usage());
$mem_real = number_format(memory_get_usage(true));

echo '$num : '.$num. ' , $array_length : '.$array_length.' , $num * $array_length : '.  number_format($num * $array_length). PHP_EOL; 
echo '$mem : '.$mem.PHP_EOL;
echo '$mem_real : '.$mem_real.PHP_EOL;
echo '$csv : '.  number_format(mb_strlen($csv, 'ASCII')).PHP_EOL;




$ php -d memory_limit=-1 array_mem.php 

$num : 20000 , $array_length : 20 , $num * $array_length : 400,000
$mem : 65,813,688
$mem_real : 66,584,576
$csv : 1,000,000

使用量がもとのcsvの約1MBに対して約65MBメモリ食います。orz
昔、元のcsvのサイズの2倍くらいオーバーヘッドあっても大丈夫だろうとおもってこんな感じのコードを書いたらみごとにmemory exhaustedしたのを思い出したのでメモ。

でもですね…

ここまで書いたもののcsvの容量のぞくと64MBっていう、
なんだか意味ありげな数字だったんで
もしやと思って次のようにコード変えて$value_sizeを変えてくと、
どうも1023あたりがもっともメモリ効率がいいらしく、
それより少なければ少ないほど無駄にメモリを確保されてました。
大きい場合は全体サイズが大きくなっていくので、
だんだんオーバーヘッドが相対的に小さくなっている模様。

おそらくintのときは8バイト確保しているせいかとはおもいますが、
stringのときは1023+\0まで確保されているっぽい?

csvとして1KB近い値っていうのは実用的に少し考えづらいので、
いずれにせよメモリ効率は良くないですね…
時間のあるときにphpのソースの配列のところの処理を追ってみたいところですね。

ちなみにphpのバージョンは5.5.14 (64bit版)でした。

以下検証コードと結果です。

<?php
// filename: array_mem.php 
$list = [];
$csv = '';
$num = 20000;
$array_length = 20;
$value_size = 1023;
foreach(range(0, $num-1) as $idx=>$current ){
	//$item = range(0,$array_length-1);
	
	foreach(range(0,$array_length-1) as $k=>$v){
		$item[$k] = str_repeat('z',$value_size);
	}
	$list[] = $item;
	$csv .= implode(',', $item).PHP_EOL;
}

$mem = memory_get_usage();
$mem_real = memory_get_usage(true);
$csv_size = mb_strlen($csv, 'ASCII');
echo '$num: '.$num. ', $array_length: '.$array_length.', $value_size: '.$value_size.', $num * $array_length: '.  number_format($num * $array_length). PHP_EOL; 
echo '$mem: '.number_format($mem).PHP_EOL;
echo '$mem_real: '.number_format($mem_real).PHP_EOL;
echo '$csv: '.  number_format($csv_size).PHP_EOL;
echo '$mem/$csv_size: '.  round($mem/$csv_size,2).PHP_EOL;

$value_size=1

php -d memory_limit=-1 array_mem.php 
$num: 20000, $array_length: 20, $value_size: 1, $num * $array_length: 400,000
$mem: 78,417,952
$mem_real: 81,788,928
$csv: 800,000
$mem/$csv_size: 98.02

$value_size=31

$ php -d memory_limit=-1 array_mem.php 
$num: 20000, $array_length: 20, $value_size: 31, $num * $array_length: 400,000
$mem: 96,814,856
$mem_real: 97,517,568
$csv: 12,800,000
$mem/$csv_size: 7.56

$value_size=255

$ php -d memory_limit=-1 array_mem.php 
$num: 20000, $array_length: 20, $value_size: 255, $num * $array_length: 400,000
$mem: 276,019,760
$mem_real: 276,824,064
$csv: 102,400,000
$mem/$csv_size: 2.7

$value_size=511

$ php -d memory_limit=-1 array_mem.php 
$num: 20000, $array_length: 20, $value_size: 511, $num * $array_length: 400,000
$mem: 480,818,376
$mem_real: 481,820,672
$csv: 204,800,000
$mem/$csv_size: 2.35

$value_size=1022

$ php -d memory_limit=-1 array_mem.php 
$num: 20000, $array_length: 20, $value_size: 1022, $num * $array_length: 400,000
$mem: 890,034,256
$mem_real: 890,765,312
$csv: 409,200,000

$value_size=1023

$ php -d memory_limit=-1 array_mem.php 
$num: 20000, $array_length: 20, $value_size: 1023, $num * $array_length: 400,000
$mem: 890,434,256
$mem_real: 891,289,600
$csv: 409,600,000
$mem/$csv_size: 2.17

$value_size=1024

$ php -d memory_limit=-1 array_mem.php 
$num: 20000, $array_length: 20, $value_size: 1024, $num * $array_length: 400,000
$mem: 894,024,272
$mem_real: 895,221,760
$csv: 410,000,000
$mem/$csv_size: 2.18

$value_size=2047

$ php -d memory_limit=-1 array_mem.php 
$num: 20000, $array_length: 20, $value_size: 2047, $num * $array_length: 400,000
$mem: 1,709,645,080
$mem_real: 1,710,751,744
$csv: 819,200,000
$mem/$csv_size: 2.09

// end of snippet.