Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions benchmark/array-count/README.md
Original file line number Diff line number Diff line change
@@ -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.
12 changes: 12 additions & 0 deletions benchmark/array-count/benchmark.php
Original file line number Diff line number Diff line change
@@ -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";
}
5 changes: 5 additions & 0 deletions benchmark/array-count/project.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name: array-count-benchmark
build-mode: bin
optimize: 2
sources:
- benchmark.php
28 changes: 28 additions & 0 deletions benchmark/array-count/results-arm64.json
Original file line number Diff line number Diff line change
@@ -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
]
}
}
5 changes: 5 additions & 0 deletions benchmark/array-count/run.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

require __DIR__ . '/benchmark.php';

main($argc, $argv);
13 changes: 13 additions & 0 deletions phpunit/code/count-known-array.php
Original file line number Diff line number Diff line change
@@ -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);
}
8 changes: 8 additions & 0 deletions phpunit/src/CountLiteralFoldTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ public function testArgumentUnpackingUsesTheRuntimeCallPath(): void
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
{
global $translator;
Expand Down
8 changes: 8 additions & 0 deletions src/Optimizer/FuncCallOptimizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -1329,6 +1329,14 @@ 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->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) {
return 'php::fn::count(' . $this->getArg($e, 0) . ', ' . $this->convertIntExpr($this->getArg($e, 1)) . ')';
}
Expand Down
29 changes: 29 additions & 0 deletions tests/compiler/array/count-known-array.phpt
Original file line number Diff line number Diff line change
@@ -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