Tags:, Posted in PHP Leave a Comment

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を用いるとメモリコピーが行われること、値そのものへのアクセスは参照渡し(リファレンス渡し)の方が寧ろコストがかかることなどを検証しるので、参考にしてください。

このエントリーをはてなブックマークに追加
Bookmark this on Yahoo Bookmark
Bookmark this on Livedoor Clip
Share on FriendFeed
Share on StumbleUpon
Newsing it!

Related posts:

  1. [PHP] 高速化Tipsのオカルト(2) echoとprint
  2. [PHP] bitの異なるOS間でのserializedデータ交換
  3. [PHP] 論理演算子「and, or」と「&&, ||」の違い

2009 年 6 月 5 日