PHPでの関数への値渡し/参照渡しは、C言語などのそれと基本的には同様ですが、若干の高級言語らしいトリックが含まれています。
少し前ですが、新たにジョインしたメンバーT君(PHPは初心者)が、レスポンスを気にして参照渡しを多用してくれていたので、ちょっと説明。
値渡しでも、ポインタが渡される
T君は、関数に大きな配列を渡す際に参照渡しにしてくれていました。
メモリコピーのオーバーヘッドを無くすためです。
function get_value(array &$a){ return $a[0]; } $a = get_big_array(); $v = get_value($a);
こういった意識・心がけは非常に良いことなのですが、実はPHPにおいて、このケースは値渡しでも参照渡しでも同じ挙動をします。
確認してみましょう。
function byVal(array $a){ echo memory_get_usage()."\n"; } function byRef(array &$a){ echo memory_get_usage()."\n"; } $a = get_big_array(); echo memory_get_usage()."\n"; byRef($a); byVal($a);
$aは50kほどの大きさです。値渡しの際にメモリコピーが行われるならば、byVal関数を呼んだ際にメモリ使用量が50kくらい増えるはずです。
実行結果を見てみましょう。
2538312 2538408 2538456
メモリ消費量はほとんど変わっていません。
値渡しされた変数の値がコピーされるタイミング
このように、PHPでは関数に変数を渡しても、その場でコピーされるわけではありません。
しかしあくまで「値渡し」ですから、関数内のスコープで値が変更された際に、呼び出し元の変数値を変えてしまうことはありません。
つまり、値はちゃんとコピーされ(てしまい)ます。いつでしょう?
関数内で「値を変えようとした際」にコピーされるのです。
確認してみましょう。
function byVal(array $a){ $x = array_shift($a); echo memory_get_usage()."\n"; } function byRef(array &$a){ $x = array_shift($a); echo memory_get_usage()."\n"; } $a = get_big_array(); echo memory_get_usage()."\n"; byRef($a); byVal($a);
先ほどのテストとほぼ同じですが、関数内でarray_shiftを呼び、配列の値に変更が入るようにしてみます。
2538464 2539888 2586808
値渡しの際に、メモリ利用量が増えていることが確認できます。
まとめ
関数で配列やオブジェクトの値を参照だけする場合、明示的に参照渡しにする必要はありません。
寧ろ、値が変更された場合の事故を防ぐためにも、参照渡しにすべきでは無いとも言えます。
しかし一方で、渡された値に対して値渡しであることで安心して変更を加えると、思わぬオーバーヘッドが発生してしまうこともあります。
function byVal1(array $a){ $a[0] .= 'A'; return $a[0]; } function byVal2(array $a){ $x = $a[0] . 'A'; return $x; } $a = get_big_array(); $s = microtime(true); for($i=0; $i<100000; ++$i){ byVal1($a); } $e = microtime(true); echo $e-$s."\n"; $s = microtime(true); for($i=0; $i<100000; ++$i){ byVal2($a); } $e = microtime(true); echo $e-$s."\n";
30.0323519707 0.145305156708
2つの関数は結果として同じことをしていますが、経過として配列のコピーが行われるか否かで200倍以上の差が出ています。
こういった点を注意しようと思えば、結局のところ挙動をしっかりと意識しなければなりません。
- 実行結果の事故を防ぐため、必要に迫られない場合は値渡しにする
- 値渡しであっても、不必要にコピーが行われないように意識する
という、ある意味矛盾した方針でコーディングしていくのが良いと思います。>T君
追記
メモリ利用量ではなくパフォーマンスの角度から、別のエントリを書きました。
関数内でforeachを用いるとメモリコピーが行われること、値そのものへのアクセスは参照渡し(リファレンス渡し)の方が寧ろコストがかかることなどを検証しるので、参考にしてください。
Related posts: