PHPのメモリ節約と参照渡しについて

概要

今回は以前から調べようと思って調べきれてなかった、参照渡しとを行うことでメモリの節約をできるのかということについてまとめたいと思います。

トピックとしては下記のような。

  • コピーオンライト
  • 参照カウンタ、参照フラグ

トピックおさらい

PHPでは代入を行う際に基本的に何もしなければ値をコピーして渡します。
参照を渡したい場合は&をつけることによって実現します。

$a = 'str';
$b = $a;
$c = &$a;

例えば上記のようなプログラムを作成した場合。
$bには$aのコピーが作成され、$cには$aの参照が渡されます。

ここで、「$bを使用することで、$aのコピーが作成されてしまうということはメモリが無駄になる。$cのように参照で保持しておいたほうがメモリの節約につながるのではないか」と思う方もいらっしゃるかもしれません。

今日はこの辺について考察していきます。

さて、私の考察の結論から先に書いてしまいますと「適切に参照渡しを使用することで、メモリの節約につながる」と思います。
また合わせて「特に必要がなければ参照渡しを行う必要はない。これによってメモリが無駄になることもない」とも思っています。

具体的に参照渡しするべき場所として考慮すべき時は

  • 巨大な構造を保持しているためメモリの消費を抑えたい
  • 参照元の変数(シンボル)と、参照先の変数が変更を共有できる(当たり前ですが)

逆に参照渡ししなくてもメモリの消費につながらない時とは

  • 参照先の変数で値の変更を行わない※超重要

であり、まさにここが今日のメイントピックでもあります。

調査

具体的に内部実装としてどのように先ほどの参照関係を保持しているかというと
PHP内部で参照カウントとよばれる、値(変数コンテナ)を参照しているシンボルやリンクの数(refcount)および、参照フラグという変数コンテナを参照型で保持しているシンボルが存在するかどうかという情報(is_ref)を保持しています。

例えば下記のコードに関して参照カウントと、参照フラグを見てみましょう。

$a = 'str';
$b = $a;
$c = $b;

このとき各シンボルの情報は下記のようになります。

$a => (refcount => 3, is_ref => 0)
$b => (refcount => 3, is_ref => 0)
$c => (refcount => 3, is_ref => 0)

すべての変数が同じ変数コンテナ(’str’)を参照している形になるので、納得ですね。

次に冒頭に設置したコードだとどうなるかを確認してみましょう。

$a = 'str';
$b = $a;
$c = &$b;

非常におどろくべきところなのですが、参照カウントが2となっている変数コンテナと、参照カウントが1となっている変数コンテナにわかれました。

$a => (refcount => 2, is_ref => 1)
$b => (refcount => 1, is_ref => 0)
$c => (refcount => 2, is_ref => 1)

これはどういうことなのかというと、参照渡しによりシンボルに代入を行うと、そのタイミングで別個の変数シンボルが即座に作成されます。
つまり変数コンテナのユニーク性にis_refフラグが関与しているということになります。

上記の情報を元にもう一度、参照渡ししなくてもメモリの消費につながらない時について考えてみましょう。
つまるところ、変数コンテナが作成される条件に注意していけば大丈夫です。

悪いコードとして下記の例を上げましょう(ちょっと無理矢理かな・・・)

// アプリケーションの設定項目なんかを参照で取得
$config = &$app->_config;
...
// 一旦退避とかしてみる
$tmp = $config;

上記の例では$tmpに$configを代入した段階で参照フラグの異なる変数コンテナを生成しています。
このタイミングで一気にドカンと$app_configと参照フラグのみ異なり、内容の全く同じ変数コンテナが生成されメモリを逼迫します。

結論

じゃあどうすればいいのか・・・ということなんですが。

結論、普通に使用していれば大丈夫です

さきほどの例で言うと、下記のように通常参照にすれば良いと思います。

// アプリケーションの設定項目なんかを参照で取得
$config = $app->_config;
...
// 一旦退避とかしてみる
$tmp = $config;
$tmp['url'] = $new_url;

たとえば$app->_configが配列であった場合、配列のデータの持ち方として変数コンテナをネストするような形でちょうど、親子のような関係がうまれます。
このときに上記のように一個の変数をいじるとしても、その末端の変数コンテナのみがコピー・変更されるだけで全体の他の項目は全く影響がないので、そこまでメモリなどについて心配する必要はありません。

問題が起こるのは参照渡しを保持していた時のみということです。

また冒頭のキーワードで示したようにコピーオンライトという方針に則ってメモリの最適化を図っています。
これは変数を単純にコピーした時点においては、元々あった変数の変数コンテナをずっと参照し続けます。
いざ、変更が入った段階で新しい変数コンテナを作成し、参照先をそちらに切り替えるのです。賢いですね。

ちなみにこのコピーオンライトとはプログラミングの実装以外でも、OSのメモリ管理やファイル管理にも用いられているような技術ですね。

本日はそろそろこのへんで。
参照になれば幸いです。

参考にしたページ
PHP本家


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です