PHPBLT で発表した「自作ArrayでPHPのメモリ節約」のダイジェスト版です。
環境
MacOSX 標準の PHP 5.5.29
(PHP7 だと話が違ってくるのでよろしく)
PHP はメモリを沢山使う
例えば、たった1000個程度の配列で 141KB も使います。
<?php
$m = memory_get_usage();
$a = array();
for ($i = 0 ; $i < 1000; $i++) {
$a[] = 100;
}
echo memory_get_usage() - $m;
=> 144736 (141 KB)
SplFixedArray で半分まで減る
$m = memory_get_usage();
$a = new SplFixedArray(1000);
for ($i = 0 ; $i < 1000; $i++) {
$a[$i] = 100;
}
echo memory_get_usage() - $m;
=> 56624 (55 KB)
蛇足ですが、SplFixedArray は Fixed の名前通り固定長なのは良いとして、array_〜 系関数が使えないのが結構困りますね。困りすぎるので詳細を後の方に書きます。
PHP の配列がメモリを沢山使う理由
- PHP の変数にはどんな型が入るか分からないので、それらを受け入れる十分大きな容れ物としてメモリを確保する。いわゆる変数コンテナのサイズが大きい
- 標準の Array は連想配列として添え字に文字列も入れて使えるような複雑な管理構造データを持つ。
- この件は裏PHP Advent の方に書きます。
自作 PHP Array
アイデア
自分のようなバイナリのバイト要素を配列に落として。といった使い方をする場合、配列要素に 0〜255 の値しか入れないので 1要素に1byteしか使わず、例え 1万個の要素があっても 10000 = (9KB) 程度で済むはず。
PHP の string の文字として格納すれば、最小限のメモリ使用量で済みそう。
実装の仕方
ArrayAccess, Countable, Iterator の3つを実装すれば、配列っぽいクラスを自作出来る。
class Uint8Array implements ArrayAccess,
Countable,
Iterator
- ArrayAccess => $a[〜] のように添え字でアクセス出来る。配列として最低限の機能
- Countable => count($a) で要素数がわかる。これがないと for ループも回せない
- Iterator => foreach($a as 〜) でループが回せる。配列ならこれはやりたい。
詳細は以下のコードを読んでください。
結果
composer require yoya/array_typed
で利用できます。
require 'vendor/autoload.php';
$m = memory_get_usage();
$a = new Array_Uint8(1000);
for ($i = 0 ; $i < 1000; $i++) {
$a[$i] = 100;
}
echo memory_get_usage() - $m;
=> 39776 (38 KB)
メモリ使用量を大幅削減できました。
注意点
SplFixedArray と共通する話ですが、ArrayAccess, Countable, Iterator を実装したとしても、あくまでも配列風のクラスであって、PHP標準arrayと全く同じようには使えません。
まず array_〜 関数が使えません。toArray(これはSplFixedArrayにはあります) と slice メソッド位は自前で実装した方が良さそうです。(現状の ArrayTyped パッケージは slice が手抜き実装です、後でちゃんと作ります。。)
is_array で引っかかりません。あくまで is_object 扱いです。instanceof ArrayAccess なら引っかかります。または場合によって厳密に instanceof Uint8Array 等とすると良いでしょう。ちなみに instanceof SplFixedArray も is_object なので is_array には引っかかりません。
最後に SplFixedArray の結構大きなデメリット。
要素を参照渡しできないので、例えば配列でツリー構造を表現すると、再帰的に処理するのが面倒になります。
具体的にいうと、PHPの標準array では以下のような処理ができますが、SplFixedArrray だと "Indirect modification of overloaded element of SplFixedArray has no effect" エラーになります。
<?php
$arr = [1,2,[31,32,33], 4];
add($arr);
print_r($arr);
function add(&$arr) {
if (is_array($arr)) {
foreach ($arr as &$e) {
add($e);
}
} else {
$arr += 1;
}
}
SplFixedArray はツリーのリーフ(末端)でだけ使うのが無難でしょう。今回自作した TypedArray はそもそも配列をネスト出来ないので関係ない話ですが、一応うんちくとして。
以上です。