1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

reflection: Use fast ZPP for ReflectionProperty::(get|set)Value() (#16329)

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
This commit is contained in:
Tim Düsterhus
2024-10-10 09:19:53 +02:00
committed by GitHub
parent df8f298d8d
commit 3da6818c9e

View File

@@ -4014,9 +4014,8 @@ ZEND_METHOD(ReflectionClassConstant, getValue)
reflection_object *intern; reflection_object *intern;
zend_class_constant *ref; zend_class_constant *ref;
if (zend_parse_parameters_none() == FAILURE) { ZEND_PARSE_PARAMETERS_NONE();
RETURN_THROWS();
}
GET_REFLECTION_OBJECT_PTR(ref); GET_REFLECTION_OBJECT_PTR(ref);
zval *name = reflection_prop_name(ZEND_THIS); zval *name = reflection_prop_name(ZEND_THIS);
@@ -6033,7 +6032,6 @@ ZEND_METHOD(ReflectionProperty, setValue)
{ {
reflection_object *intern; reflection_object *intern;
property_reference *ref; property_reference *ref;
zval *object;
zval *value; zval *value;
zval *tmp; zval *tmp;
@@ -6064,11 +6062,13 @@ ZEND_METHOD(ReflectionProperty, setValue)
zend_update_static_property_ex(intern->ce, ref->unmangled_name, value); zend_update_static_property_ex(intern->ce, ref->unmangled_name, value);
} else { } else {
if (zend_parse_parameters(ZEND_NUM_ARGS(), "oz", &object, &value) == FAILURE) { zend_object *object;
RETURN_THROWS(); ZEND_PARSE_PARAMETERS_START(2, 2)
} Z_PARAM_OBJ(object)
Z_PARAM_ZVAL(value)
ZEND_PARSE_PARAMETERS_END();
zend_update_property_ex(intern->ce, Z_OBJ_P(object), ref->unmangled_name, value); zend_update_property_ex(intern->ce, object, ref->unmangled_name, value);
} }
} }
/* }}} */ /* }}} */