Created
August 16, 2026 08:19
-
-
Save staabm/127abcf5aafc47d0b178eda39e525ad2 to your computer and use it in GitHub Desktop.
return_type_check_elision_repro.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| /* | |
| * Reproducer for commit 95d3ed1af81930f06b8e72b0bf414e52b1353a31: | |
| * "Elide provably-passing return type checks at compile time" | |
| * | |
| * Run this script on a build before and after that commit, ideally with: | |
| * ./sapi/cli/php -n -dopcache.enable_cli=0 return_type_check_elision_repro.php | |
| * | |
| * The "direct" cases should get faster after the commit because | |
| * ZEND_VERIFY_RETURN_TYPE is not emitted for provably safe returns. | |
| */ | |
| if (PHP_SAPI !== 'cli') { | |
| fwrite(STDERR, "Run from CLI.\n"); | |
| exit(1); | |
| } | |
| final class GetterBench | |
| { | |
| private int $x = 7; | |
| public function direct(): int | |
| { | |
| return $this->x; | |
| } | |
| public function control(): int | |
| { | |
| $tmp = $this->x; | |
| return $tmp; | |
| } | |
| } | |
| function cmp_direct(int $a, int $b): bool | |
| { | |
| return $a < $b; | |
| } | |
| function cmp_control(int $a, int $b): bool | |
| { | |
| $tmp = $a < $b; | |
| return $tmp; | |
| } | |
| function run_getter_direct(int $iterations): int | |
| { | |
| $obj = new GetterBench(); | |
| $sum = 0; | |
| for ($i = 0; $i < $iterations; $i++) { | |
| $sum += $obj->direct(); $sum += $obj->direct(); $sum += $obj->direct(); $sum += $obj->direct(); | |
| $sum += $obj->direct(); $sum += $obj->direct(); $sum += $obj->direct(); $sum += $obj->direct(); | |
| } | |
| return $sum; | |
| } | |
| function run_getter_control(int $iterations): int | |
| { | |
| $obj = new GetterBench(); | |
| $sum = 0; | |
| for ($i = 0; $i < $iterations; $i++) { | |
| $sum += $obj->control(); $sum += $obj->control(); $sum += $obj->control(); $sum += $obj->control(); | |
| $sum += $obj->control(); $sum += $obj->control(); $sum += $obj->control(); $sum += $obj->control(); | |
| } | |
| return $sum; | |
| } | |
| function run_cmp_direct(int $iterations): int | |
| { | |
| $sum = 0; | |
| $end = $iterations + 3; | |
| for ($i = 0; $i < $iterations; $i++) { | |
| $sum += cmp_direct($i, $end) ? 1 : 0; $sum += cmp_direct($i + 1, $end) ? 1 : 0; | |
| $sum += cmp_direct($i + 2, $end) ? 1 : 0; $sum += cmp_direct($i + 3, $end) ? 1 : 0; | |
| $sum += cmp_direct($i + 4, $end) ? 1 : 0; $sum += cmp_direct($i + 5, $end) ? 1 : 0; | |
| $sum += cmp_direct($i + 6, $end) ? 1 : 0; $sum += cmp_direct($i + 7, $end) ? 1 : 0; | |
| } | |
| return $sum; | |
| } | |
| function run_cmp_control(int $iterations): int | |
| { | |
| $sum = 0; | |
| $end = $iterations + 3; | |
| for ($i = 0; $i < $iterations; $i++) { | |
| $sum += cmp_control($i, $end) ? 1 : 0; $sum += cmp_control($i + 1, $end) ? 1 : 0; | |
| $sum += cmp_control($i + 2, $end) ? 1 : 0; $sum += cmp_control($i + 3, $end) ? 1 : 0; | |
| $sum += cmp_control($i + 4, $end) ? 1 : 0; $sum += cmp_control($i + 5, $end) ? 1 : 0; | |
| $sum += cmp_control($i + 6, $end) ? 1 : 0; $sum += cmp_control($i + 7, $end) ? 1 : 0; | |
| } | |
| return $sum; | |
| } | |
| function run_case(string $label, int $iterations, int $rounds, callable $fn): array | |
| { | |
| $times = []; | |
| $sink = 0; | |
| for ($w = 0; $w < 2; $w++) { | |
| $sink ^= $fn($iterations); | |
| } | |
| for ($r = 0; $r < $rounds; $r++) { | |
| $start = hrtime(true); | |
| $sink ^= $fn($iterations); | |
| $times[] = (hrtime(true) - $start) / 1e9; | |
| } | |
| sort($times); | |
| return [ | |
| 'label' => $label, | |
| 'best' => $times[0], | |
| 'avg' => array_sum($times) / count($times), | |
| 'sink' => $sink, | |
| ]; | |
| } | |
| $iterations = isset($argv[1]) ? max(1, (int) $argv[1]) : 5_000_000; | |
| $rounds = isset($argv[2]) ? max(1, (int) $argv[2]) : 7; | |
| gc_disable(); | |
| $cases = [ | |
| run_case('getter-direct', $iterations, $rounds, 'run_getter_direct'), | |
| run_case('getter-control', $iterations, $rounds, 'run_getter_control'), | |
| run_case('cmp-direct', $iterations, $rounds, 'run_cmp_direct'), | |
| run_case('cmp-control', $iterations, $rounds, 'run_cmp_control'), | |
| ]; | |
| $byLabel = []; | |
| foreach ($cases as $row) { | |
| $byLabel[$row['label']] = $row; | |
| } | |
| printf( | |
| "PHP %s\niterations=%d rounds=%d calls_per_round=%d\n\n", | |
| PHP_VERSION, | |
| $iterations, | |
| $rounds, | |
| $iterations * 8 | |
| ); | |
| echo str_pad('case', 16) . str_pad('best(s)', 12) . str_pad('avg(s)', 12) . "sink\n"; | |
| echo str_repeat('-', 50) . "\n"; | |
| foreach ($cases as $row) { | |
| printf( | |
| "%-16s%-12.6f%-12.6f%d\n", | |
| $row['label'], | |
| $row['best'], | |
| $row['avg'], | |
| $row['sink'] | |
| ); | |
| } | |
| echo "\nDirect/control ratio inside each pair (best time, lower is better):\n"; | |
| printf( | |
| "%-16s %.4fx\n", | |
| 'getter', | |
| $byLabel['getter-direct']['best'] / $byLabel['getter-control']['best'] | |
| ); | |
| printf( | |
| "%-16s %.4fx\n", | |
| 'cmp', | |
| $byLabel['cmp-direct']['best'] / $byLabel['cmp-control']['best'] | |
| ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment