関数の引数の処理
関数の引数の処理についても速くなっています。関数の解析に使われている関数zend_parse_parameters()がCPUの5%を占めていました。これは、zend_parse_parameters()の引数に与えている、その関数の引数仕様を理解するために、scanf()のように文字列を解析しているからです。具体的には内部で以下のようなコードになっています。
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
"za|b",
&value,
&array,
&strict) == FAILURE) {
return;
}
PHP 7になって、互換性を保つために、この関数も残されていますが、パフォーマンスが高いマクロも作られました。前述のコードは以下のように書き換えられます。
ZEND_PARSE_PARAMETERS_START() Z_PARAM_ZVAL(value) Z_PARAM_ARRAY(array) Z_PARAM_OPTIONAL Z_PARAM_BOOL(strict) ZEND_PARSE_PARAMETERS_END();
これだけではイメージできないので、中をみていきましょう。zend_parse_parameters()はこのようになっています。
……
for (spec_walk = type_spec; *spec_walk; spec_walk++) {
c = *spec_walk;
switch (c) {
case 'l': case 'd':
case 's': case 'b':
case 'r': case 'a':
case 'o': case 'O':
case 'z': case 'Z':
case 'C': case 'h':
case 'f': case 'A':
case 'H': case 'p':
case 'S': case 'P':
case 'L':
max_num_args++;
break;
case '|':
min_num_args = max_num_args;
break;
case '/':
case '!':
/* Pass */
break;
case '*':
case '+':
……
default:
……
}
}
……
以上のように、文字を1文字ずつ読んでいき、解析しながら進めています。少しずつの積み重ねでも、関数を呼ぶたびに解析していくので負荷が高まっていくでしょう。一方、マクロで書くと以下のようになります。
……
do { \
if (UNEXPECTED(_num_args < _min_num_args) || \
(UNEXPECTED(_num_args > _max_num_args) && \
EXPECTED(_max_num_args >= 0))) { \
if (!(_flags & ZEND_PARSE_PARAMS_QUIET)) { \
zend_wrong_paramers_count_error(_num_args, _min_num_args, _max_num_args); \
} \
error_code = ZPP_ERROR_FAILURE; \
break; \
} \
_i = 0; \
_real_arg = ZEND_CALL_ARG(execute_data, 0);
……
if (separate) { \
Z_PARAM_PROLOGUE(separate); \
zend_parse_arg_zval_deref(_arg, &dest, check_null); \
} else { \
if (UNEXPECTED(++_i >_num_args)) break; \
_real_arg++; \
zend_parse_arg_zval(_real_arg, &dest, check_null); \
}
……
if (UNEXPECTED(!zend_parse_arg_array(_arg, &dest, check_null, 0))) { \
_expected_type = Z_EXPECTED_ARRAY; \
error_code = ZPP_ERROR_WRONG_ARG; \
break; \
}
_optional = 1;
……
} while (0); \
if (UNEXPECTED(error_code != ZPP_ERROR_OK)) { \
if (!(_flags & ZEND_PARSE_PARAMS_QUIET)) { \
if (error_code == ZPP_ERROR_WRONG_CALLBACK) { \
zend_wrong_callback_error(E_WARNING, _i, _error); \
} else if (error_code == ZPP_ERROR_WRONG_CLASS) { \
zend_wrong_paramer_class_error(_i, _error, _arg); \
} else if (error_code == ZPP_ERROR_WRONG_ARG) { \
zend_wrong_paramer_type_error(_i, _expected_type, _arg); \
} \
} \
failure; \
} \
} while (0)
……
行末に\が付いているのは、マクロなので1行に収める必要があるからです。do while文になっていますが、条件式が0となっているのでループしません。では、なぜdo while文になっているかというと、途中でbreakして抜け出せるようにするためです。引数の型を順番に取得していっているだけなので、zend_parse_parameters()と比べるとだいぶシンプルになっています。文字列を解析していくわけではなく、決められた順番で決められた型を処理していくので、処理が早くなっているのです。
まとめ
zval、ハッシュテーブル、関数の引数の処理とみてきましたが、PHP 5とPHP 7は機構が違ってきていることが分かります。メモリを使った方がよいところはメモリを使っていますが、全体的に無駄はなくなっているように思います。個人的には、PHP 5のzvalは、すべての型に対して同じような構造を有しており、美しいと思っていたので好きでした。PHP 7では、その美しさを捨てて、型を差別化することによって最適化して速くなったと言えるでしょう。
