三項演算子より if の方が良いこともある

[PHP] 三項演算子より if の方が良いこともある

追記 2013-06-24

PHP は別の変数でもメモリ上は同じ実体を指していたりすることがあるため*1、状況によっては三項演算子より if で分岐した方がいいよー、という話。

下記の string_cast_? は単に与えられた引数を文字列にキャストするだけの関数ですが、引数が巨大な文字列であれば string_cast_4 とそれ以外で性能が明らかに違ってきます。

<?php
function string_cast_1($str)
{
	return (string)$str;
}

function string_cast_2($str)
{
	return strval($str);
}

function string_cast_3($str)
{
	return is_string($str) ? $str : (string)$str;
}

function string_cast_4($str)
{
	if (is_string($str))
	{
		return $str;
	}
	else
	{
		return (string)$str;
	}
}

string_cast_3 と string_cast_4 は同じ事のような気もしますが、引数が文字列の場合に string_cast_4 では引数と戻り値が同じ実体になりますが string_cast_3 の場合はそうはならないため、文字列のコピーが行われてしまいます*2


下記のようなコードで違いが顕著に現れます。string_cast_4 以外だとメモリ使用量のピーク値が呼び出し後に 10MB ぐらい増えますが string_cast_4 ではピーク値に変化はありません。実行速度も 10MB のメモリのコピーをするかしないかの違いがあるので極端に異なります。

<?php
$a = str_repeat("x", 1024*1024*10); // 10 MB

$i = 0;
$m = 0;

$t = microtime(true) + 1;
$m = memory_get_peak_usage(false);

for(; microtime(true) < $t; $i++)
{
	// どれか1つコメントイン
	//$a = string_cast_1($a);
	//$a = string_cast_2($a);
	//$a = string_cast_3($a);
	//$a = string_cast_4($a);
}

// メモリ使用量のピーク値と 1 秒間に実行できた回数を表示
$m = memory_get_peak_usage(false) - $m;
echo "$m\t$i\n";


下記のようなコードで refcount の値を比較を比較してみるとわかりやすいです。

<?php
$a = "string_cast_3";
$b = string_cast_3($a);
debug_zval_dump($a);
debug_zval_dump($b);

$a = "string_cast_4";
$b = string_cast_4($a);
debug_zval_dump($a);
debug_zval_dump($b);

string_cast_3 の場合は refcount(2)、string_cast_4 の場合は refcount(3)、となります。debug_zval_dump の呼び出しの中で refcount が 1 増えるので実際には refcount(1) と refcount(2) と考えて問題ありません。string_cast_3 の場合 $a と $b は別々の実体となりますが、string_cast_4 は $a と $b が同じ実体になるため参照数が 1 つ大きくなります。


巨大な文字列やバイナリデータを扱う場合は、この辺りを意識すればメモリ使用量や実行速度の面でお得です。

追記 2013-06-24

この現象は PHP 5.3 までの話で、PHP 5.4 だと string_cast_3 でも string_cast_4 でも変わらないようです。

*1:PHP は、というか大抵の言語でそうだと思いますが

*2:同じ実体と言ってもいわゆる PHP の参照とは異なります