Skip to content

Commit c4a7d06

Browse files
committed
reflection: Use fast ZPP for ReflectionProperty::(get|set)Value()
During the Doctrine Core Team Meetup 2024 the Doctrine team investigated the performance overhead of using `setRawValueWithoutLazyInitialization()` instead of `setValue()` and came to the surprising conclusion that `setRawValueWithoutLazyInitialization()` outperformed `setValue()`, despite doing more work. These two scripts are used as the benchmark: <?php class Foo { public $id; public $foo1; public $foo2; public $foo3; public $foo4; } $reflection = new ReflectionClass(Foo::class); $properties = $reflection->getProperties(); for ($i = 0; $i < 1000000; $i++) { $foo = new Foo(); foreach ($properties as $property) { $property->setValue($foo, 1); } } and <?php class Foo { public $id; public $foo1; public $foo2; public $foo3; public $foo4; } $reflection = new ReflectionClass(Foo::class); $properties = $reflection->getProperties(); for ($i = 0; $i < 1000000; $i++) { $foo = new Foo(); foreach ($properties as $property) { $property->setRawValueWithoutLazyInitialization($foo, 1); } } Benchmarking these with a current git master shows that `setValue()` is 50% slower: $ hyperfine -L script setValue,setRawValueWithoutLazyInitialization '/tmp/php-before /tmp/test/{script}.php' Benchmark 1: /tmp/php-before /tmp/test/setValue.php Time (mean ± σ): 216.0 ms ± 5.8 ms [User: 212.0 ms, System: 3.7 ms] Range (min … max): 208.2 ms … 225.3 ms 13 runs Benchmark 2: /tmp/php-before /tmp/test/setRawValueWithoutLazyInitialization.php Time (mean ± σ): 145.6 ms ± 3.6 ms [User: 141.6 ms, System: 3.8 ms] Range (min … max): 140.4 ms … 152.8 ms 20 runs Summary /tmp/php-before /tmp/test/setRawValueWithoutLazyInitialization.php ran 1.48 ± 0.05 times faster than /tmp/php-before /tmp/test/setValue.php Looking into the “why” revealed that the `setValue()` script spent quite some time in `zend_parse_parameters()`. A 50% overhead can be significant, given that `setValue()` is commonly called several thousand times in a single request when using Doctrine. This commit changes the non-static property case of `setValue()` to make use of the fast parameter parsing API and adjusts `getValue()` for consistency. The resulting comparison shows that both `setValue()` and `setRawValueWithoutLazyInitialization()` are now (almost) equal: $ hyperfine -L script setValue,setRawValueWithoutLazyInitialization 'sapi/cli/php /tmp/test/{script}.php' Benchmark 1: sapi/cli/php /tmp/test/setValue.php Time (mean ± σ): 143.0 ms ± 6.4 ms [User: 139.4 ms, System: 3.4 ms] Range (min … max): 134.8 ms … 157.7 ms 18 runs Benchmark 2: sapi/cli/php /tmp/test/setRawValueWithoutLazyInitialization.php Time (mean ± σ): 147.0 ms ± 5.5 ms [User: 143.0 ms, System: 3.6 ms] Range (min … max): 139.9 ms … 159.8 ms 19 runs Summary sapi/cli/php /tmp/test/setValue.php ran 1.03 ± 0.06 times faster than sapi/cli/php /tmp/test/setRawValueWithoutLazyInitialization.php
1 parent b675db4 commit c4a7d06

File tree

1 file changed

+8
-8
lines changed

1 file changed

+8
-8
lines changed

ext/reflection/php_reflection.c

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4014,9 +4014,8 @@ ZEND_METHOD(ReflectionClassConstant, getValue)
40144014
reflection_object *intern;
40154015
zend_class_constant *ref;
40164016

4017-
if (zend_parse_parameters_none() == FAILURE) {
4018-
RETURN_THROWS();
4019-
}
4017+
ZEND_PARSE_PARAMETERS_NONE();
4018+
40204019
GET_REFLECTION_OBJECT_PTR(ref);
40214020

40224021
zval *name = reflection_prop_name(ZEND_THIS);
@@ -6033,7 +6032,6 @@ ZEND_METHOD(ReflectionProperty, setValue)
60336032
{
60346033
reflection_object *intern;
60356034
property_reference *ref;
6036-
zval *object;
60376035
zval *value;
60386036
zval *tmp;
60396037

@@ -6064,11 +6062,13 @@ ZEND_METHOD(ReflectionProperty, setValue)
60646062

60656063
zend_update_static_property_ex(intern->ce, ref->unmangled_name, value);
60666064
} else {
6067-
if (zend_parse_parameters(ZEND_NUM_ARGS(), "oz", &object, &value) == FAILURE) {
6068-
RETURN_THROWS();
6069-
}
6065+
zend_object *object;
6066+
ZEND_PARSE_PARAMETERS_START(2, 2)
6067+
Z_PARAM_OBJ(object)
6068+
Z_PARAM_ZVAL(value)
6069+
ZEND_PARSE_PARAMETERS_END();
60706070

6071-
zend_update_property_ex(intern->ce, Z_OBJ_P(object), ref->unmangled_name, value);
6071+
zend_update_property_ex(intern->ce, object, ref->unmangled_name, value);
60726072
}
60736073
}
60746074
/* }}} */

0 commit comments

Comments
 (0)