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
61 changes: 61 additions & 0 deletions bench/node_name_resolver_is_string_name_bench.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

/**
* Benchmark for NodeNameResolver::isStringName().
*
* On a non-matching name (the common case - a rule tests isName against many candidates, most
* fail) the method falls through to a wildcard-character guard. The array-const + str_contains
* loop scanned the needle once per wildcard char; a single strpbrk() over the char set does it
* in one C-level pass with identical throw behaviour.
*
* Run on both branches to compare:
* php bench/node_name_resolver_is_string_name_bench.php
*
* Reports CPU time (user+sys) and wall time; CPU is the reliable metric, wall is noisy
* under concurrent load.
*/

use Rector\DependencyInjection\LazyContainerFactory;
use Rector\NodeNameResolver\NodeNameResolver;

require __DIR__ . '/../vendor/autoload.php';

const CALLS = 200_000; // isStringName() calls per run
const REPEAT = 60; // outer repeats

$rectorConfig = (new LazyContainerFactory())->create();
$rectorConfig->boot();

/** @var NodeNameResolver $nodeNameResolver */
$nodeNameResolver = $rectorConfig->make(NodeNameResolver::class);

// non-matching, wildcard-free pairs - the hot fall-through path to the guard
$resolved = 'App\\Service\\SomeLongClassName';
$desired = 'App\\Service\\OtherLongClassName';

$cpuSeconds = static function (): float {
$usage = getrusage();
return $usage['ru_utime.tv_sec'] + $usage['ru_utime.tv_usec'] / 1_000_000
+ $usage['ru_stime.tv_sec'] + $usage['ru_stime.tv_usec'] / 1_000_000;
};

$run = static function () use ($nodeNameResolver, $resolved, $desired): void {
for ($i = 0; $i < CALLS; ++$i) {
$nodeNameResolver->isStringName($resolved, $desired);
}
};

$run(); // warm up, excluded

$cpuStart = $cpuSeconds();
$wallStart = hrtime(true);
for ($repeat = 0; $repeat < REPEAT; ++$repeat) {
$run();
}

printf('NodeNameResolver::isStringName benchmark%s', PHP_EOL);
printf(' calls=%d repeat=%d%s', CALLS, REPEAT, PHP_EOL);
printf('%s', PHP_EOL);
printf(' isStringName cpu=%.4fs wall=%.4fs%s', $cpuSeconds() - $cpuStart, (hrtime(true) - $wallStart) / 1_000_000_000, PHP_EOL);
12 changes: 5 additions & 7 deletions src/NodeNameResolver/NodeNameResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ final class NodeNameResolver
/**
* Used to check if a string might contain a regex or fnmatch pattern
*/
private const array REGEX_WILDCARD_CHARS = ['*', '#', '~', '/'];
private const string REGEX_WILDCARD_CHARS = '*#~/';

/**
* @var array<string, NodeNameResolverInterface|null>
Expand Down Expand Up @@ -181,12 +181,10 @@ public function isStringName(string $resolvedName, string $desiredName): bool
return true;
}

foreach (self::REGEX_WILDCARD_CHARS as $char) {
if (str_contains($desiredName, $char)) {
throw new ShouldNotHappenException(
'Matching of regular expressions is no longer supported. Use $this->getName() and compare with e.g. str_ends_with() or str_starts_with() instead.'
);
}
if (strpbrk($desiredName, self::REGEX_WILDCARD_CHARS) !== false) {
throw new ShouldNotHappenException(
'Matching of regular expressions is no longer supported. Use $this->getName() and compare with e.g. str_ends_with() or str_starts_with() instead.'
);
}

return false;
Expand Down
Loading