Skip to content

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

Merged
TimWolla merged 1 commit into
php:masterfrom
TimWolla:reflection-property-getsetvalue-fast-zpp
Oct 10, 2024
Merged

reflection: Use fast ZPP for ReflectionProperty::(get|set)Value()#16329
TimWolla merged 1 commit into
php:masterfrom
TimWolla:reflection-property-getsetvalue-fast-zpp

Conversation

@TimWolla

Copy link
Copy Markdown
Member

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

@ndosschendossche left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise LGTM.

Comment threadext/reflection/php_reflection.c Outdated
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
@TimWolla
TimWollaforce-pushed the reflection-property-getsetvalue-fast-zpp branch from ea58e73 to c4a7d06CompareOctober 10, 2024 06:44
@TimWolla
TimWolla merged commit 3da6818 into php:masterOct 10, 2024
@TimWolla
TimWolla deleted the reflection-property-getsetvalue-fast-zpp branch October 10, 2024 07:19
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@TimWolla@ndossche