perf(compiler): bypass dynamic dispatch for known-array appends (#97)

master
yavon007 1 month ago committed by GitHub
parent 12b0781914
commit d1136cfca0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 35
      benchmark/array-append/README.md
  2. 15
      benchmark/array-append/benchmark.php
  3. 5
      benchmark/array-append/project.yml
  4. 28
      benchmark/array-append/results-arm64.json
  5. 5
      benchmark/array-append/run.php
  6. 2
      phpunit/code/hot-path-codegen.php
  7. 8
      phpunit/src/HotPathCodegenTest.php
  8. 3
      src/Parser/AssignOpTrait.php
  9. 72
      tests/compiler/operator/array-append-expression.phpt

@ -0,0 +1,35 @@
# Known-array append benchmark
This benchmark appends a function result to a known array, 1,000 times per
round, and accumulates a checksum. It isolates the append fallback that must
materialize its RHS before writing. It does not represent every PHP workload.
With PHP 8.5 and a matching PHPX/embed installation, from this directory:
```sh
php run.php 100000
php ../../bin/tpc.php project.yml --no-progress -o array_append_benchmark
./array_append_benchmark 100000
```
The checksum must be `200000000`. Build baseline and candidate revisions into
separate build directories and binary paths. Keep PHPX, PHP, compiler flags,
and machine fixed. Alternate binary execution order, discard a warm-up pair,
and compare medians across multiple runs; exclude compilation time.
## Sample result
Linux ARM64 in Docker, PHP 8.5.10 ZTS, PHPX `6a68f38`, GCC `-O2`, no LTO;
baseline TypePHP `692841a6` versus direct known-array append lowering.
Nine measured runs per binary after one discarded pair, 100,000 rounds:
| Build | Median | Range |
| --- | ---: | ---: |
| Baseline | 980.88 ms | 970.47–1018.14 ms |
| Direct append | 563.60 ms | 558.99–582.68 ms |
Elapsed time decreased by 42.54% for this append-heavy microbenchmark; all
nine paired runs were faster. This is not an application-wide speedup or a
comparison against native PHP. Measurements include process startup and were
collected on a shared development machine. Raw times in seconds are in
`results-arm64.json`; its `count` field is the number of rounds.

@ -0,0 +1,15 @@
<?php
function makeAppendValue(int $value): int { return $value + 1; }
function main(int $argc, array $argv): void
{
$rounds = $argc > 1 ? (int) $argv[1] : 10000;
$checksum = 0;
for ($r = 0; $r < $rounds; ++$r) {
$items = [];
for ($i = 0; $i < 1000; ++$i) {
$items[] = makeAppendValue($i);
}
$checksum += count($items) + $items[999];
}
echo $checksum, "\n";
}

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

@ -0,0 +1,28 @@
{
"count": 100000,
"checksum": "200000000",
"samples": {
"candidate": [
0.571479212,
0.582684347,
0.558988057,
0.561899686,
0.564578895,
0.560435413,
0.559544357,
0.563601629,
0.566580885
],
"baseline": [
0.970465081,
0.979752269,
0.98223335,
0.983323284,
0.99910999,
0.976687971,
0.977219854,
0.980881162,
1.018139124
]
}
}

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

@ -13,6 +13,8 @@ function hotPathCodegen(int $limit): int
$items[0] = $value;
$items[] = $value;
$items[] = hotPathTrace('append');
$appendResult = ($items[] = $value);
$items[0] += $value;
$items[0] += $other[0];
$items[2] = $other[0];

@ -16,6 +16,14 @@ final class HotPathCodegenTest extends \BaseTest
self::assertStringContainsString('items.offsetSet(0L,', $code);
}
public function testKnownArrayAppendFallbackAvoidsDynamicDispatch(): void
{
$code = $this->compileFixture();
self::assertStringNotContainsString('items.offsetSet(php::null,', $code);
self::assertMatchesRegularExpression('/items\.append\(tmp_var_\d+\)/', $code);
}
public function testSafeTwoOperandConcatAndExactStringArgumentStayUnboxed(): void
{
$code = $this->compileFixture();

@ -76,6 +76,9 @@ trait AssignOpTrait
return $code . $array . '.appendValue(' . $value . ')';
}
$tmp = $this->addTmpVar(Type::VAR);
if ($arrayType === Type::ARRAY) {
return $code . '((' . $tmp . ' = ' . $value . ', ' . "{$array}.append({$tmp})" . '), ' . $tmp . ')';
}
return $code . '((' . $tmp . ' = ' . $value . ', ' . "{$array}.offsetSet(" . self::VALUE_NULL . ", {$tmp})" . '), ' . $tmp . ')';
}
$dim = $this->parseIdentifier($left->dim);

@ -0,0 +1,72 @@
--TEST--
Known-array append expressions preserve values, references and RHS mutations
--FILE--
<?php
function nextValue(array &$items): int
{
$items[] = 10;
return count($items);
}
function main(): void
{
$source = std::any(1);
$reference =& $source;
$items = [];
$result = ($items[] = $reference);
$reference = 2;
var_dump($items, $result);
$items[] =& $reference;
$reference = 3;
var_dump($items, $result);
$copy = $items;
$value = ($items[] = nextValue($items));
var_dump($items, $copy, $value);
$nested = [&$reference];
$last = ($items[] = $nested[0]);
$reference = 4;
var_dump($items, $last);
}
?>
--EXPECT--
array(1) {
[0]=>
int(1)
}
int(1)
array(2) {
[0]=>
int(1)
[1]=>
&int(3)
}
int(1)
array(4) {
[0]=>
int(1)
[1]=>
&int(3)
[2]=>
int(10)
[3]=>
int(3)
}
array(2) {
[0]=>
int(1)
[1]=>
&int(3)
}
int(3)
array(5) {
[0]=>
int(1)
[1]=>
&int(4)
[2]=>
int(10)
[3]=>
int(3)
[4]=>
int(3)
}
int(3)
Loading…
Cancel
Save