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
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

.github export-ignore
apigen export-ignore
benchmarks export-ignore
phpcs.xml export-ignore
doc export-ignore
tests export-ignore
Expand All @@ -17,3 +18,4 @@ Makefile export-ignore
phpstan-baseline.neon export-ignore
phpstan.neon export-ignore
phpunit.xml export-ignore
tally.php export-ignore
57 changes: 57 additions & 0 deletions .github/workflows/benchmarks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# https://help.github.com/en/categories/automating-your-workflow-with-github-actions

name: "Benchmarks"

on:
pull_request:
push:
branches:
- "2.4.x"

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true

jobs:
benchmarks:
name: "Benchmarks"
# Tally's counts depend on the system libraries and the PHP build, so both stay fixed:
# a new image or PHP release means accepting new numbers
runs-on: "ubuntu-24.04"

steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
with:
egress-policy: audit

- name: "Checkout"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: "Install PHP"
uses: "shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962" # v2
with:
coverage: "none"
php-version: "8.5"
extensions: "posix"

- name: "Install dependencies"
run: "composer install --no-interaction --no-progress"

- name: "Install Tally"
run: "make bench-install"

- name: "Run the benchmarks"
run: "php tools/tally/vendor/bin/tally run --output=tmp/tally-results"

- name: "Upload the results"
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: "tally-results"
path: "tmp/tally-results"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
/composer.lock
.phpunit.result.cache
/.amp/portals/*
/tools/tally/vendor
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ make check
- `make lint` - Check PHP syntax only
- `make cs` - Check code style only
- `make phpstan-generate-baseline` - Generate PHPStan baseline (use sparingly)
- `make bench-install`, then `make bench` - Count the benchmarks in `benchmarks/` with [Tally](https://github.com/phpstan/tally) (needs PHP 8.2 on Linux). They cover parsing large and typical PHPDocs. Take their expected numbers from the Benchmarks workflow, not from a local run: a benchmark without numbers gets a patch in the job's output

## Workflow Summary

Expand Down
13 changes: 13 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,16 @@ phpstan-generate-baseline:
.PHONY: grammars-install
grammars-install:
composer install --no-interaction --working-dir tools/phplrt

# ---------------------------------------------------------------------------
# The benchmarks
# ---------------------------------------------------------------------------

# Tally is a development dependency of its own too, because it needs PHP 8.2.
.PHONY: bench-install
bench-install:
composer install --no-interaction --working-dir tools/tally

.PHONY: bench
bench:
php tools/tally/vendor/bin/tally run
39 changes: 39 additions & 0 deletions benchmarks/LexerBench.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php declare(strict_types = 1);

namespace PHPStan\PhpDocParser\Benchmarks;

use PHPStan\PhpDocParser\Lexer\Lexer;
use PHPStan\PhpDocParser\ParserConfig;
use Tally\Attribute\Before;
use Tally\Attribute\Expected;
use Tally\Attribute\Warmup;

/**
* Lexing typical PHPDocs, which 148ceff made about 40 % quicker by extracting the matches
* with PREG_PATTERN_ORDER after e59b0c8 made the numeric-separator groups non-capturing.
*/
final class LexerBench
{

private Lexer $lexer;

/** @var list<string> */
private array $phpDocs;

#[Before]
public function prepare(): void
{
$this->lexer = new Lexer(new ParserConfig(['lines' => true]));
$this->phpDocs = require __DIR__ . '/data/vendor-docblocks.php';
}

#[Warmup(1)]
#[Expected(instructions: '25.27M', cycles: '26.59M', memory: '108.0KiB')]
public function benchTokenizeTypicalPhpDocs(): void
{
foreach ($this->phpDocs as $phpDoc) {
$this->lexer->tokenize($phpDoc);
}
}

}
50 changes: 50 additions & 0 deletions benchmarks/SymfonyConfigReferenceBench.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php declare(strict_types = 1);

namespace PHPStan\PhpDocParser\Benchmarks;

use PHPStan\PhpDocParser\Lexer\Lexer;
use PHPStan\PhpDocParser\Parser\ConstExprParser;
use PHPStan\PhpDocParser\Parser\PhpDocParser;
use PHPStan\PhpDocParser\Parser\TokenIterator;
use PHPStan\PhpDocParser\Parser\TypeParser;
use PHPStan\PhpDocParser\ParserConfig;
use Tally\Attribute\Before;
use Tally\Attribute\Expected;
use Tally\Attribute\Warmup;

/**
* Parsing the PHPDocs of a config/reference.php that Symfony 7.4 generates, as PHPStan does:
* one large array shape with many generics and constant types. 1427af4 made it about twice as
* quick by computing the token offsets once, 3fcdcf0 twice as quick again by not walking to
* the end of the PHPDoc for every generic.
*/
final class SymfonyConfigReferenceBench
{

private Lexer $lexer;

private PhpDocParser $phpDocParser;

/** @var list<string> */
private array $phpDocs;

#[Before]
public function prepare(): void
{
$config = new ParserConfig(['lines' => true]);
$constExprParser = new ConstExprParser($config);
$this->lexer = new Lexer($config);
$this->phpDocParser = new PhpDocParser($config, new TypeParser($config, $constExprParser), $constExprParser);
$this->phpDocs = require __DIR__ . '/data/symfony-config-reference.php';
}

#[Warmup(1)]
#[Expected(instructions: '98.66M', cycles: '112.5M', memory: '4.123MiB')]
public function benchParse(): void
{
foreach ($this->phpDocs as $phpDoc) {
$this->phpDocParser->parse(new TokenIterator($this->lexer->tokenize($phpDoc)));
}
}

}
Loading
Loading