From 8e07af45f7f44fb006a2fdfb99524f1dbcf5f6d2 Mon Sep 17 00:00:00 2001 From: yavon007 Date: Tue, 8 Sep 2026 12:58:45 +0800 Subject: [PATCH 1/2] perf(optimizer): inline count for known array variables --- benchmark/array-count/README.md | 34 +++++++++++++++++++++ benchmark/array-count/benchmark.php | 12 ++++++++ benchmark/array-count/project.yml | 5 +++ benchmark/array-count/results-arm64.json | 28 +++++++++++++++++ benchmark/array-count/run.php | 5 +++ phpunit/code/count-known-array.php | 8 +++++ phpunit/src/CountLiteralFoldTest.php | 8 +++++ src/Optimizer/FuncCallOptimizer.php | 7 +++++ tests/compiler/array/count-known-array.phpt | 29 ++++++++++++++++++ 9 files changed, 136 insertions(+) create mode 100644 benchmark/array-count/README.md create mode 100644 benchmark/array-count/benchmark.php create mode 100644 benchmark/array-count/project.yml create mode 100644 benchmark/array-count/results-arm64.json create mode 100644 benchmark/array-count/run.php create mode 100644 phpunit/code/count-known-array.php create mode 100644 tests/compiler/array/count-known-array.phpt diff --git a/benchmark/array-count/README.md b/benchmark/array-count/README.md new file mode 100644 index 00000000..c3610194 --- /dev/null +++ b/benchmark/array-count/README.md @@ -0,0 +1,34 @@ +# Known-array count benchmark + +Each iteration updates one of 100 array keys and adds `count($items)` to a +checksum. Mutation prevents measuring only a loop-invariant count. This is +an isolated array-update/count workload, not an application-wide benchmark. + +From this directory with PHP 8.5 and a matching PHPX/embed installation: + +```sh +php run.php 30000000 +php ../../bin/tpc.php project.yml --no-progress -o array_count_benchmark +./array_count_benchmark 30000000 +``` + +Expected checksum: `2999995050`. Compile baseline and candidate into separate +binary paths and build directories, keeping PHPX and compiler flags fixed. +Alternate execution order, discard a warm-up pair, and compare medians. + +## Sample result + +Linux ARM64 in Docker, PHP 8.5.10 ZTS, PHPX `6a68f38`, GCC `-O2`, no LTO; +TypePHP baseline `692841a6` versus direct known-array count lowering. +30 million iterations, nine measured pairs after one discarded warm-up: + +| Build | Median | Range | +| --- | ---: | ---: | +| Baseline | 319.63 ms | 301.22–345.40 ms | +| Direct count | 192.73 ms | 185.74–196.94 ms | + +Elapsed time decreased 39.70%; all nine pairs were faster and checksums +matched. Raw samples in seconds are in `results-arm64.json`. Measurements +include process startup and exclude compilation, on a shared development +machine. They do not imply the same application-wide improvement or a +speedup against native PHP. diff --git a/benchmark/array-count/benchmark.php b/benchmark/array-count/benchmark.php new file mode 100644 index 00000000..6f9cf229 --- /dev/null +++ b/benchmark/array-count/benchmark.php @@ -0,0 +1,12 @@ + 1 ? (int) $argv[1] : 10000000; + $items = []; + $checksum = 0; + for ($i = 0; $i < $iterations; ++$i) { + $items[$i % 100] = $i; + $checksum += count($items); + } + echo $checksum, "\n"; +} diff --git a/benchmark/array-count/project.yml b/benchmark/array-count/project.yml new file mode 100644 index 00000000..33fc35fb --- /dev/null +++ b/benchmark/array-count/project.yml @@ -0,0 +1,5 @@ +name: array-count-benchmark +build-mode: bin +optimize: 2 +sources: + - benchmark.php diff --git a/benchmark/array-count/results-arm64.json b/benchmark/array-count/results-arm64.json new file mode 100644 index 00000000..050cf37a --- /dev/null +++ b/benchmark/array-count/results-arm64.json @@ -0,0 +1,28 @@ +{ + "count": 30000000, + "checksum": "2999995050", + "samples": { + "candidate": [ + 0.185741445, + 0.190759172, + 0.192725389, + 0.193156391, + 0.193159849, + 0.186731365, + 0.19180551, + 0.196935488, + 0.193052223 + ], + "baseline": [ + 0.32846526, + 0.330373976, + 0.301215819, + 0.345402035, + 0.31430037, + 0.335472037, + 0.315937627, + 0.306001963, + 0.319634016 + ] + } +} diff --git a/benchmark/array-count/run.php b/benchmark/array-count/run.php new file mode 100644 index 00000000..2ffee2dc --- /dev/null +++ b/benchmark/array-count/run.php @@ -0,0 +1,5 @@ +compileToCpp('count-known-array.php'); + + self::assertStringContainsString('static_cast(items.count())', $cpp); + self::assertSame(2, substr_count($cpp, 'php::fn::count(')); + } + private function compileToCpp(string $file): string { global $translator; diff --git a/src/Optimizer/FuncCallOptimizer.php b/src/Optimizer/FuncCallOptimizer.php index c3d2a2fe..d55e451b 100644 --- a/src/Optimizer/FuncCallOptimizer.php +++ b/src/Optimizer/FuncCallOptimizer.php @@ -1329,6 +1329,13 @@ protected function genCount(string $n, Node\Expr\FuncCall $e, array $c): string| $folded = $this->doFoldCountLiteral($e); if ($folded !== false) return $folded; + if (count($e->args) === 1 + && $receiver instanceof Node\Arg + && $this->isVarExpr($receiver->value) + && $this->argumentAlreadyHasExactType($receiver->value, Type::ARRAY) + ) { + return 'static_cast<' . Type::INT . '>(' . $this->getArg($e, 0) . '.count())'; + } if (count($e->args) >= 2) { return 'php::fn::count(' . $this->getArg($e, 0) . ', ' . $this->convertIntExpr($this->getArg($e, 1)) . ')'; } diff --git a/tests/compiler/array/count-known-array.phpt b/tests/compiler/array/count-known-array.phpt new file mode 100644 index 00000000..e35f8862 --- /dev/null +++ b/tests/compiler/array/count-known-array.phpt @@ -0,0 +1,29 @@ +--TEST-- +Known-array count tracks mutation and preserves recursive and dynamic counting +--FILE-- + +--EXPECT-- +int(0) +int(2) +int(4) +int(1) +int(-1) +int(2) +int(1) +int(2) From 3277e616e46b27df05d610eea0378a45aa3da52d Mon Sep 17 00:00:00 2001 From: yavon007 Date: Tue, 8 Sep 2026 13:00:10 +0800 Subject: [PATCH 2/2] fix(optimizer): exclude global wrappers from direct array count --- phpunit/code/count-known-array.php | 5 +++++ phpunit/src/CountLiteralFoldTest.php | 2 +- src/Optimizer/FuncCallOptimizer.php | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/phpunit/code/count-known-array.php b/phpunit/code/count-known-array.php index 04212ddd..b70737bc 100644 --- a/phpunit/code/count-known-array.php +++ b/phpunit/code/count-known-array.php @@ -6,3 +6,8 @@ function knownArrayCount(array $items, mixed $dynamic): int $unknown = count($dynamic); return $normal + $recursive + $unknown; } + +function globalArrayCount(): int +{ + return count($GLOBALS) + count($_SERVER); +} diff --git a/phpunit/src/CountLiteralFoldTest.php b/phpunit/src/CountLiteralFoldTest.php index f8abbb2f..effa21d4 100644 --- a/phpunit/src/CountLiteralFoldTest.php +++ b/phpunit/src/CountLiteralFoldTest.php @@ -50,7 +50,7 @@ public function testKnownArrayVariableUsesDirectCount(): void $cpp = $this->compileToCpp('count-known-array.php'); self::assertStringContainsString('static_cast(items.count())', $cpp); - self::assertSame(2, substr_count($cpp, 'php::fn::count(')); + self::assertSame(4, substr_count($cpp, 'php::fn::count(')); } private function compileToCpp(string $file): string diff --git a/src/Optimizer/FuncCallOptimizer.php b/src/Optimizer/FuncCallOptimizer.php index d55e451b..2a28700a 100644 --- a/src/Optimizer/FuncCallOptimizer.php +++ b/src/Optimizer/FuncCallOptimizer.php @@ -1332,6 +1332,7 @@ protected function genCount(string $n, Node\Expr\FuncCall $e, array $c): string| if (count($e->args) === 1 && $receiver instanceof Node\Arg && $this->isVarExpr($receiver->value) + && $this->hasLocalVar($receiver->value->name) && $this->argumentAlreadyHasExactType($receiver->value, Type::ARRAY) ) { return 'static_cast<' . Type::INT . '>(' . $this->getArg($e, 0) . '.count())';