PHP internal functions use zend_parse_parameters() API to receive values of actual parameters into C variables. This function uses scanf() like approach for parameter definition. The number and types of required data defined by a string that contains a list of specifiers (āsā - for string, ālā for long, etc). Unfortunately, this string has to be parsed each time when php calls this function, and this makes significant performance overhead.
For example zend_parse_parameters() takes ~6% of CPU time in serving wordpress home page.
For some really simple function like is_string() or ord() the overhead of zend_parse_parameters() may be about 90%.
We propose an additional fast API for parameter parsing, that should be used for the most useful functions. The API is based on C macros that lead to inlining of optimized code directly into the body of internal function.
We don't propose to remove the existing API, and would suggest to use fast API only for most often used functions to get performance boost.
I'll explain API on the following example:
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "al|zb", &input, &offset, &z_length, &preserve_keys) == FAILURE) { return; }
ZEND_PARSE_PARAMETERS_START(2, 4) Z_PARAM_ARRAY(input) Z_PARAM_LONG(offset) Z_PARAM_OPTIONAL Z_PARAM_ZVAL(z_length) Z_PARAM_BOOL(preserve_keys) ZEND_PARSE_PARAMETERS_END();
The first code fragment is just taken from PHP_FUNCTION(array_slice), the second is its replacement using new API. The code is actually self explainable.
ZEND_PARSE_PARAMETERS_START() takes two arguments minimal and maximal parameters count.
Z_PARAM_ARRAY() - takes the next argument as array, Z_PARAM_LONG - as long, Z_PARAM_OPTIONAL - tells that the remaining arguments are optional.
The new API covers all possibilities of the existing API. The following table shows the correspondence between old specifiers and new macros.
specifier | Fast ZPP API macro | args |
---|---|---|
| | Z_PARAM_OPTIONAL | |
a | Z_PARAM_ARRAY(dest) | dest - zval* |
A | Z_PARAM_ARRAY_OR_OBJECT(dest) | dest - zval* |
b | Z_PARAM_BOOL(dest) | dest - zend_bool |
C | Z_PARAM_CLASS(dest) | dest - zend_class_entry* |
d | Z_PARAM_DOUBLE(dest) | dest - double |
f | Z_PARAM_FUNC(fci, fcc) | fci - zend_fcall_info, fcc - zend_fcall_info_cache |
h | Z_PARAM_ARRAY_HT(dest) | dest - HashTable* |
H | Z_PARAM_ARRAY_OR_OBJECT_HT(dest) | dest - HashTable* |
l | Z_PARAM_LONG(dest) | dest - long |
L | Z_PARAM_STRICT_LONG(dest) | dest - long |
o | Z_PARAM_OBJECT(dest) | dest - zval* |
O | Z_PARAM_OBJECT_OF_CLASS(dest, ce) | dest - zval* |
p | Z_PARAM_PATH(dest, dest_len) | dest - char*, dest_len - int |
P | Z_PARAM_PATH_STR(dest) | dest - zend_string* |
r | Z_PARAM_RESOURCE(dest) | dest - zval* |
s | Z_PARAM_STRING(dest, dest_len) | dest - char*, dest_len - int |
S | Z_PARAM_STR(dest) | dest - zend_string* |
z | Z_PARAM_ZVAL(dest) | dest - zval* |
Z_PARAM_ZVAL_DEREF(dest) | dest - zval* | |
+ | Z_PARAM_VARIADIC('+', dest, num) | dest - zval*, num int |
* | Z_PARAM_VARIADIC('*', dest, num) | dest - zval*, num int |
The effect of ! and reference modifiers may be achieved using extended version of the same macros e.g. Z_PARAM_ZVAL_EX(dest, check_null, separate).
The following numbers collected for 100 and 1000 requests to wordpress home page.
# of CPU instructions | time [sec] | |
---|---|---|
phpng | 5,132,181,672 | 18.90 |
phpng + fast_zpp | 4,901,907,204 | 18.40 |
So the proposal provides 4.5% improvement in terms of CPU instructions and 2.5% in terms of time.
The effect of code size increase is negligible. Just ~20KB for ~70 function (that's below 0.1%)
The new API provides exactly the same behavior. Also, we don't propose to remove the old API. So there must not be any compatibility problems.
This proposal is targeted to php-7
State whether this project requires 50%+1 majority (see voting). The vote is a straight Yes/No vote.
The vote concluded on January 27th.
The patch is already included into PHP-7 and wrapped with ifdef/ifndef FAST_ZPP.