徹底検証!PHP最適化Tips

第3回演算子と関数の噂を検証

はじめに

今回は演算子に焦点をあて、同様の動きをする演算子や関数と比較検証を行います。

‘==’‘===’

‘==⁠⁠===⁠とを比較します。⁠===⁠は値だけではなく型も含めて比較を行う演算子です。

まずはベンチマークを取ってみます。

benchmark_equal.php
<?php
$t = microtime(true);
$i = 0;
while($i < 1000) {
   if ('a' == 'b') {}
   ++$i;        
}
$tmp = microtime(true) - $t;
var_dump($tmp);
?>
benchmark_identical.php
<?php
$t = microtime(true);
$i = 0;
while($i < 1000) {
   if ('a' === 'b') {}
   ++$i;        
}
$tmp = microtime(true) - $t;
var_dump($tmp);
?>
実行結果
$ php benchmark_equal.php
float(0.00020694732666)
$ php benchmark_identical.php
float(0.000159978866577)

‘===⁠のほうが⁠==⁠よりも僅かに速い結果となりました。

それでは実装を見ていきましょう。まずは⁠==⁠です。

‘==⁠の実体はZend/zend_operators.c内のis_equal_function()にあります。is_equal_function()の中をみると、compare_functionで左辺と右辺の比較を行っていることが分かります。

1539 ZEND_API int is_equal_function(zval *result, zval *op1, zval *op2 TSRMLS_DC)
1540 {
1541     if (compare_function(result, op1, op2 TSRMLS_CC) == FAILURE) {
1542         return FAILURE;
1543     }
1544     convert_to_boolean(result);
1545     if (result->value.lval == 0) {
1546         result->value.lval = 1;
1547     } else {
1548         result->value.lval = 0;
1549     }
1550     return SUCCESS;
1551 }

次にcompare_function()を見ていきます。同じくzend_operators.cに書かれてます。

1323 ZEND_API int compare_function(zval *result, zval *op1, zval *op2 TSRMLS_DC)

文字列と数値との比較について見ていきます。

1390     if (op1->type == IS_STRING && op2->type == IS_STRING) {
1391         zendi_smart_strcmp(result, op1, op2);
1392         COMPARE_RETURN_AND_FREE(SUCCESS);
1393     }

文字列の場合、zendi_smart_strcmp()で文字列比較が行われます。この関数では文字列が数字のみでできた文字列かを変換しチェックを行い、その結果によって比較処理が異なります。

1417     if (Z_TYPE_P(op1) == IS_LONG && Z_TYPE_P(op2) == IS_LONG) {
1418         ZVAL_LONG(result, Z_LVAL_P(op1)>Z_LVAL_P(op2)?1:(Z_LVAL_P(op1)<Z_LVAL_P(op2)?-1:0));
1419         COMPARE_RETURN_AND_FREE(SUCCESS);
1420     }

数値の場合、型がlong同士であればそのまま比較します。

1421     if ((Z_TYPE_P(op1) == IS_DOUBLE || Z_TYPE_P(op1) == IS_LONG)
1422         && (Z_TYPE_P(op2) == IS_DOUBLE || Z_TYPE_P(op2) == IS_LONG)) {
1423         Z_DVAL_P(result) = (Z_TYPE_P(op1) == IS_LONG ? (double) Z_LVAL_P(op1) : Z_DVAL_P(op1)) - (Z_TYPE_P(op2) == IS_LONG ? (double) Z_LVAL_P(op2) : Z_DVAL_P(op2));
1424         ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_DVAL_P(result)));
1425         COMPARE_RETURN_AND_FREE(SUCCESS);
1426     }

数値の型が違うときはlongなほうの値をdoubleに変換して比較が行われます。

一方、⁠===⁠の実体はZend/zend_operators.c内のis_identical_function()にあります。文字列と数値の場合についての関数の動きを見ていきます。

1471 ZEND_API int is_identical_function(zval *result, zval *op1, zval *op2 TSRMLS_DC)
1472 {
1473     result->type = IS_BOOL;
1474     if (op1->type != op2->type) {
1475         result->value.lval = 0;
1476         return SUCCESS;
1477     }

1474から1477行目でまず型のチェックを行い、型が違えばfalseを返して終了、同じなら値のチェックに入ります。

1478行目から型によってそれぞれの値のチェックが行われます。

1478     switch (op1->type) {
1479         case IS_NULL:
1480             result->value.lval = (op2->type==IS_NULL);
1481             break;

型がnullの場合はもう片方の値の型もnullかどうか比較します。

1482         case IS_BOOL:
1483         case IS_LONG:
1484         case IS_RESOURCE:
1485             result->value.lval = (op1->value.lval == op2->value.lval);
1486             break;
1487         case IS_DOUBLE:
1488             result->value.lval = (op1->value.dval == op2->value.dval);
1489             break;

1482から1489行目ではbool, long, resource, double型についてのチェックが行われ、値はC言語の⁠==⁠演算子で比較が行われます。

1490         case IS_STRING:
1491             if ((op1->value.str.len == op2->value.str.len)
1492                 && (!memcmp(op1->value.str.val, op2->value.str.val,  op1->value.str.len))) {
1493                 result->value.lval = 1;
1494             } else {
1495                 result->value.lval = 0;
1496             }
1497             break;

1490行目からはstringの比較をしていて、文字列長の比較とmemcmpによる文字列の比較が行われます。

ここまででis_identical_function()の解説は終わりです。

‘==⁠では型を考慮しないので、違う型での比較をすることも考えた凝った構造に対して、⁠===⁠では同じ型かを最初にチェックして同じ型の値同士で比較を行うため、シンプルな構造で余計な型の変換もありません。そのため、⁠===⁠のほうが速い結果になったと考えられます。

is_nullと‘===’

is_null()は与えた引数がnullであるかどうかをチェックする関数です。 is_**関数はほかにもたくさんあり、いろいろとタイプをチェックする関数が用意されています。

is_bool
booleanであるかを調べる
is_resource
リソースかどうかを調べる
is_long
整数型かどうかを調べる
is_float
float型かどうかを調べる
is_string
文字列かどうかを調べる
is_array
配列かどうかを調べる
is_object
オブジェクトかどうかを調べる
is_numeric
数字または数値形式の文字列かどうかを調べる
is_callable
関数としてコール可能な構造であるかどうかを調べる

まずはベンチマークを取ってみます。

benchmark_identical2.php
<?php
$t = microtime(true);
$i = 0;
$a = null;
while($i < 1000) {
   if ($a === null) {}
   ++$i;        
}
$tmp = microtime(true) - $t;
var_dump($tmp);
?>
benchmark_is_null.php
<?php
$t = microtime(true);
$i = 0;
$a = null;
while($i < 1000) {
   if (is_null($a)) {}
   ++$i;
}
$tmp = microtime(true) - $t;
var_dump($tmp);
?>
$ php benchmark_identical2.php
float(0.000123023986816)
$ php benchmark_is_null.php
float(0.00032114982605)

‘===⁠のほうがis_null()よりも速い結果になりました。

is_null()の実装を見ていきましょう。is_null()はext/standard/type.cにあり、php_is_type()で実際の処理が行われています。

240 PHP_FUNCTION(is_null)
241 {
242     php_is_type(INTERNAL_FUNCTION_PARAM_PASSTHRU, IS_NULL);
243 }
203 static void php_is_type(INTERNAL_FUNCTION_PARAMETERS, int type)
204 {
205     zval **arg;
206 
207     if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &arg) == FAILURE) {
208         php_error_docref(NULL TSRMLS_CC, E_WARNING, "Only one argument expected");
209         RETURN_FALSE;
210     }
211 
212     if (Z_TYPE_PP(arg) == type) {
      中略
231         RETURN_TRUE;
232     } else {
233         RETURN_FALSE;
234     }
235 }

php_is_type()の中での処理は、207行目でis_null()に渡された引数を取得して212行目で渡された引数とチェックすべきタイプ(ここではnullかどうか)をチェックし、その結果を返しています。

php_is_type()はis_bool, is_longでは引数のtypeにIS_BOOL、IS_LONGを渡して呼ばれ、汎用的に使われます。

is_null()の中身を見ると、ボトルネックになっているような箇所はなさそうなので、演算子のほうが関数よりも速いためにis_null()よりも⁠===⁠のほうが速い結果になったと言えます。

おすすめ記事

記事・ニュース一覧