perf(optimizer): inline count for known array variables (#98)

* perf(optimizer): inline count for known array variables

* fix(optimizer): exclude global wrappers from direct array count
master
yavon007 1 month ago committed by GitHub
parent 10fbfff4ea
commit f3e2e32648
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 34
      benchmark/array-count/README.md
  2. 12
      benchmark/array-count/benchmark.php
  3. 5
      benchmark/array-count/project.yml
  4. 28
      benchmark/array-count/results-arm64.json
  5. 5
      benchmark/array-count/run.php
  6. 13
      phpunit/code/count-known-array.php
  7. 8
      phpunit/src/CountLiteralFoldTest.php
  8. 8
      src/Optimizer/FuncCallOptimizer.php
  9. 29
      tests/compiler/array/count-known-array.phpt

@ -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.

@ -0,0 +1,12 @@
<?php
function main(int $argc, array $argv): void
{
$iterations = $argc > 1 ? (int) $argv[1] : 10000000;
$items = [];
$checksum = 0;
for ($i = 0; $i < $iterations; ++$i) {
$items[$i % 100] = $i;
$checksum += count($items);
}
echo $checksum, "\n";
}

@ -0,0 +1,5 @@
name: array-count-benchmark
build-mode: bin
optimize: 2
sources:
- benchmark.php

@ -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
]
}
}

@ -0,0 +1,5 @@
<?php
require __DIR__ . '/benchmark.php';
main($argc, $argv);

@ -0,0 +1,13 @@
<?php
function knownArrayCount(array $items, mixed $dynamic): int
{
$normal = count($items);
$recursive = count($items, COUNT_RECURSIVE);
$unknown = count($dynamic);
return $normal + $recursive + $unknown;
}
function globalArrayCount(): int
{
return count($GLOBALS) + count($_SERVER);
}

@ -45,6 +45,14 @@ class CountLiteralFoldTest extends TestCase
self::assertStringNotContainsString('php::fn::count(', $cpp); self::assertStringNotContainsString('php::fn::count(', $cpp);
} }
public function testKnownArrayVariableUsesDirectCount(): void
{
$cpp = $this->compileToCpp('count-known-array.php');
self::assertStringContainsString('static_cast<php::Int>(items.count())', $cpp);
self::assertSame(4, substr_count($cpp, 'php::fn::count('));
}
private function compileToCpp(string $file): string private function compileToCpp(string $file): string
{ {
global $translator; global $translator;

@ -1329,6 +1329,14 @@ trait FuncCallOptimizer
$folded = $this->doFoldCountLiteral($e); $folded = $this->doFoldCountLiteral($e);
if ($folded !== false) return $folded; if ($folded !== false) return $folded;
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())';
}
if (count($e->args) >= 2) { if (count($e->args) >= 2) {
return 'php::fn::count(' . $this->getArg($e, 0) . ', ' . $this->convertIntExpr($this->getArg($e, 1)) . ')'; return 'php::fn::count(' . $this->getArg($e, 0) . ', ' . $this->convertIntExpr($this->getArg($e, 1)) . ')';
} }

@ -0,0 +1,29 @@
--TEST--
Known-array count tracks mutation and preserves recursive and dynamic counting
--FILE--
<?php
function main(): void
{
$items = [];
var_dump(count($items));
$items[] = [1, 2];
$items['key'] = 3;
var_dump(count($items), count($items, COUNT_RECURSIVE));
unset($items['key']);
var_dump(count($items), count($items) - 2);
$copy = $items;
$items[] = 4;
var_dump(count($items), count($copy));
$dynamic = std::any($items);
var_dump(count($dynamic));
}
?>
--EXPECT--
int(0)
int(2)
int(4)
int(1)
int(-1)
int(2)
int(1)
int(2)
Loading…
Cancel
Save