perf(compiler): group functions for declaration header generation (#129)
parent
69e2a29ec2
commit
63a4793e0c
6 changed files with 190 additions and 16 deletions
@ -0,0 +1,55 @@ |
||||
# Declaration header generation benchmark |
||||
|
||||
Creates 300 PHP source files with 6,001 functions, each ordinary function |
||||
having a default parameter. It prepares and converts the project, then measures |
||||
six calls to the real declaration-header generator and discards the first as |
||||
warm-up. The timed region includes rendering and writing headers; source |
||||
preparation, conversion, and C++ compilation are excluded. This measures one |
||||
compiler phase, not total build time. |
||||
|
||||
Run from a checkout with Composer development dependencies installed: |
||||
|
||||
```sh |
||||
project_dir=$(mktemp -d) |
||||
php benchmark/declaration-headers/run.php "$project_dir" > /tmp/declaration-candidate.log |
||||
tail -1 /tmp/declaration-candidate.log |
||||
``` |
||||
|
||||
The script writes generated sources and build outputs into the supplied |
||||
benchmark directory. Reuse the same directory for comparisons: absolute source |
||||
paths contribute to header names and therefore the combined content digest. |
||||
|
||||
The baseline does not contain this new benchmark. From the candidate checkout, |
||||
these commands create a separate baseline and run the same source in both. The |
||||
shared vendor directory is supported by `phpunit/bootstrap.php`'s checkout-local |
||||
source loader. |
||||
|
||||
```sh |
||||
baseline_dir=$(mktemp -d) |
||||
project_dir=$(mktemp -d) |
||||
git archive b3898c32 | tar -x -C "$baseline_dir" |
||||
ln -s "$(pwd)/vendor" "$baseline_dir/vendor" |
||||
mkdir -p "$baseline_dir/benchmark/declaration-headers" |
||||
cp benchmark/declaration-headers/run.php "$baseline_dir/benchmark/declaration-headers/run.php" |
||||
php "$baseline_dir/benchmark/declaration-headers/run.php" "$project_dir" > /tmp/declaration-baseline.log |
||||
php benchmark/declaration-headers/run.php "$project_dir" > /tmp/declaration-candidate.log |
||||
tail -1 /tmp/declaration-baseline.log |
||||
tail -1 /tmp/declaration-candidate.log |
||||
``` |
||||
|
||||
Compare median `ms` values and require identical `headers` counts and `sha256` |
||||
digests. The digest covers the contents and names of every generated declaration |
||||
header, including runtime and aggregate headers. |
||||
|
||||
## Sample result |
||||
|
||||
Linux ARM64 Docker, PHP 8.5.10 ZTS, CLI opcache disabled; baseline `b3898c32`: |
||||
|
||||
| Revision | Median (ms) | Headers | |
||||
| --- | ---: | ---: | |
||||
| Baseline | 88.001 | 302 | |
||||
| Grouped functions | 13.087 | 302 | |
||||
|
||||
The measured phase is about 6.72x faster, with identical output digests. Raw |
||||
samples are in `results-arm64.json`. This workload isolates function-heavy |
||||
projects; class and constant declaration scans are not optimized by this change. |
||||
@ -0,0 +1,31 @@ |
||||
{ |
||||
"environment": "Linux ARM64 Docker, PHP 8.5.10 ZTS, no CLI opcache", |
||||
"baseline_commit": "b3898c32", |
||||
"files": 300, |
||||
"functions": 6001, |
||||
"baseline": { |
||||
"ms": [ |
||||
88.000792, |
||||
87.998334, |
||||
90.341888, |
||||
90.412138, |
||||
87.537832 |
||||
], |
||||
"headers": 302, |
||||
"sha256": "bfad6f04ca81c591734aeeaf223cc32ef7064768ca12faba8ac41ef053444f37", |
||||
"median_ms": 88.000792 |
||||
}, |
||||
"candidate": { |
||||
"ms": [ |
||||
13.015777, |
||||
13.490487, |
||||
12.763525, |
||||
13.087318, |
||||
13.106777 |
||||
], |
||||
"headers": 302, |
||||
"sha256": "bfad6f04ca81c591734aeeaf223cc32ef7064768ca12faba8ac41ef053444f37", |
||||
"median_ms": 13.087318 |
||||
}, |
||||
"speedup": 6.7241272810823425 |
||||
} |
||||
@ -0,0 +1,43 @@ |
||||
<?php |
||||
require dirname(__DIR__, 2) . '/phpunit/bootstrap.php'; |
||||
if ($argc !== 2 || !is_dir($argv[1])) { |
||||
fwrite(STDERR, "Usage: php run.php <existing temporary project directory>\n"); |
||||
exit(1); |
||||
} |
||||
$root = realpath($argv[1]); |
||||
$files = []; |
||||
for ($i = 0; $i < 300; ++$i) { |
||||
$source = "<?php\n";
|
||||
for ($j = 0; $j < 20; ++$j) { |
||||
$source .= "function f_{$i}_{$j}(int \$value = 1): int { return \$value; }\n"; |
||||
} |
||||
if ($i === 0) { |
||||
$source .= "function main(): void {}\n"; |
||||
} |
||||
$file = "$root/file_$i.php"; |
||||
file_put_contents($file, $source); |
||||
$files[] = $file; |
||||
} |
||||
$translator = $compiler = TypePhp\CompilerTest::create($root); |
||||
$compiler->setTargetName('declaration_benchmark'); |
||||
(new ReflectionMethod($compiler, 'setBuildDir'))->invoke($compiler, "$root/build"); |
||||
$compiler->addFiles($files); |
||||
foreach ($files as $file) { |
||||
$compiler->prepareFile($file); |
||||
} |
||||
$compiler->convert($files); |
||||
$generate = new ReflectionMethod($compiler, 'genDeclarationHeaders'); |
||||
$samples = []; |
||||
for ($i = 0; $i < 6; ++$i) { |
||||
$start = hrtime(true); |
||||
$generate->invoke($compiler, $files); |
||||
if ($i > 0) { |
||||
$samples[] = (hrtime(true) - $start) / 1e6; |
||||
} |
||||
} |
||||
$hashes = []; |
||||
foreach (glob("$root/build/include/*decl.h") as $file) { |
||||
$hashes[basename($file)] = hash_file('sha256', $file); |
||||
} |
||||
ksort($hashes); |
||||
echo "BENCHMARK_RESULT=" . json_encode(['ms' => $samples, 'headers' => count($hashes), 'sha256' => hash('sha256', json_encode($hashes))]) . "\n"; |
||||
Loading…
Reference in new issue