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

declare(strict_types=1);

/**
* Benchmark for RenameStaticMethodRector (representative of the Renaming value-object bucket change).
*
* RenameStaticMethodRector / RenameClassConstFetchRector / RenamePropertyRector / RenameAttributeRector
* each held a flat list of value objects and scanned the whole list per visited node, running
* isName() on each entry before the object-type check. The change buckets the list by lowercased
* old name once in configure(), so a node only visits the entries sharing its name.
*
* This bench drives refactor() over many static calls whose method names are NOT in the map (the
* common case), isolating the scan cost from the object-type resolution.
*
* Run on both branches to compare:
* php bench/rename_static_method_bucket_bench.php
*
* Reports CPU time (user+sys) and wall time; CPU is the reliable metric, wall is noisy under
* concurrent load.
*/

use PhpParser\Node\Expr\StaticCall;
use Rector\DependencyInjection\LazyContainerFactory;
use Rector\PhpParser\Node\BetterNodeFinder;
use Rector\Renaming\Rector\StaticCall\RenameStaticMethodRector;
use Rector\Renaming\ValueObject\RenameStaticMethod;
use Rector\Testing\TestingParser\TestingParser;

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

const RENAMES = 400; // configured static method renames
const CALLS = 200; // static calls per pass, names not in the map
const REPEAT = 200; // outer repeats over the whole set

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

/** @var TestingParser $testingParser */
$testingParser = $rectorConfig->make(TestingParser::class);

/** @var BetterNodeFinder $betterNodeFinder */
$betterNodeFinder = $rectorConfig->make(BetterNodeFinder::class);

$rector = $rectorConfig->make(RenameStaticMethodRector::class);

$staticMethodRenames = [];
for ($i = 0; $i < RENAMES; ++$i) {
$staticMethodRenames[] = new RenameStaticMethod('Bench\\SomeClass' . $i, 'oldMethod' . $i, 'Bench\\NewClass' . $i, 'newMethod' . $i);
}

$rector->configure($staticMethodRenames);

$source = "<?php\n\nnamespace Bench;\n\n";
for ($i = 0; $i < CALLS; ++$i) {
// method names deliberately absent from the configured renames
$source .= "Other::freshCall{$i}();\n";
}

$sourceFilePath = tempnam(sys_get_temp_dir(), 'bench_') . '.php';
file_put_contents($sourceFilePath, $source);

$stmts = $testingParser->parseFileToDecoratedNodes($sourceFilePath);

/** @var StaticCall[] $staticCalls */
$staticCalls = $betterNodeFinder->findInstanceOf($stmts, StaticCall::class);

$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 ($rector, $staticCalls): void {
foreach ($staticCalls as $staticCall) {
$rector->refactor($staticCall);
}
};

// warm up autoloading/reflection caches once, excluded from timing
$run();

$wallStart = microtime(true);
$cpuStart = $cpuSeconds();

for ($repeat = 0; $repeat < REPEAT; ++$repeat) {
$run();
}

$cpu = $cpuSeconds() - $cpuStart;
$wall = microtime(true) - $wallStart;

@unlink($sourceFilePath);

$totalCalls = REPEAT * count($staticCalls);

printf('renames: %d, calls: %d, repeats: %d, refactor() calls: %d%s', RENAMES, count($staticCalls), REPEAT, $totalCalls, PHP_EOL);
printf('CPU: %.4f s (%.3f us/call)%s', $cpu, $cpu / $totalCalls * 1_000_000, PHP_EOL);
printf('wall: %.4f s%s', $wall, PHP_EOL);
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
final class RenameClassConstFetchRector extends AbstractRector implements ConfigurableRectorInterface
{
/**
* @var RenameClassConstFetchInterface[]
* @var array<string, RenameClassConstFetchInterface[]>
*/
private array $renameClassConstFetches = [];
private array $renameClassConstFetchesByOldConstant = [];

public function getRuleDefinition(): RuleDefinition
{
Expand Down Expand Up @@ -70,11 +70,12 @@ public function getNodeTypes(): array
*/
public function refactor(Node $node): ?ClassConstFetch
{
foreach ($this->renameClassConstFetches as $renameClassConstFetch) {
if (! $this->isName($node->name, $renameClassConstFetch->getOldConstant())) {
continue;
}
$constantName = $this->getName($node->name);
if ($constantName === null) {
return null;
}

foreach ($this->renameClassConstFetchesByOldConstant[strtolower($constantName)] ?? [] as $renameClassConstFetch) {
if (! $this->isObjectType($node->class, $renameClassConstFetch->getOldObjectType())) {
continue;
}
Expand All @@ -98,7 +99,11 @@ public function configure(array $configuration): void
{
Assert::allIsAOf($configuration, RenameClassConstFetchInterface::class);

$this->renameClassConstFetches = $configuration;
// bucket by lowercased old constant name, matching the case-insensitive isName() lookup
$this->renameClassConstFetchesByOldConstant = [];
foreach ($configuration as $renameClassConstFetch) {
$this->renameClassConstFetchesByOldConstant[strtolower($renameClassConstFetch->getOldConstant())][] = $renameClassConstFetch;
}
}

private function createClassAndConstFetch(RenameClassAndConstFetch $renameClassAndConstFetch): ClassConstFetch
Expand Down
17 changes: 13 additions & 4 deletions rules/Renaming/Rector/Class_/RenameAttributeRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
final class RenameAttributeRector extends AbstractRector implements ConfigurableRectorInterface, MinPhpVersionInterface
{
/**
* @var RenameAttribute[]
* @var array<string, RenameAttribute[]>
*/
private array $renameAttributes = [];
private array $renameAttributesByOldAttribute = [];

public function getRuleDefinition(): RuleDefinition
{
Expand Down Expand Up @@ -97,7 +97,11 @@ public function configure(array $configuration): void
{
Assert::allIsAOf($configuration, RenameAttribute::class);

$this->renameAttributes = $configuration;
// bucket by lowercased old attribute name (class names are case-insensitive)
$this->renameAttributesByOldAttribute = [];
foreach ($configuration as $renameAttribute) {
$this->renameAttributesByOldAttribute[strtolower($renameAttribute->getOldAttribute())][] = $renameAttribute;
}
}

public function provideMinPhpVersion(): int
Expand All @@ -107,7 +111,12 @@ public function provideMinPhpVersion(): int

private function matchNewAttributeName(Attribute $attribute): ?string
{
foreach ($this->renameAttributes as $renameAttribute) {
$attributeName = $this->getName($attribute->name);
if ($attributeName === null) {
return null;
}

foreach ($this->renameAttributesByOldAttribute[strtolower($attributeName)] ?? [] as $renameAttribute) {
if ($this->isName($attribute->name, $renameAttribute->getOldAttribute())) {
return $renameAttribute->getNewAttribute();
}
Expand Down
21 changes: 16 additions & 5 deletions rules/Renaming/Rector/PropertyFetch/RenamePropertyRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ final class RenamePropertyRector extends AbstractRector implements ConfigurableR
*/
private array $renamedProperties = [];

/**
* @var array<string, RenameProperty[]>
*/
private array $renamedPropertiesByOldProperty = [];

private bool $hasChanged = false;

public function __construct(
Expand Down Expand Up @@ -86,6 +91,12 @@ public function configure(array $configuration): void
{
Assert::allIsAOf($configuration, RenameProperty::class);
$this->renamedProperties = $configuration;

// bucket by lowercased old property name for the per-fetch path
$this->renamedPropertiesByOldProperty = [];
foreach ($configuration as $renameProperty) {
$this->renamedPropertiesByOldProperty[strtolower($renameProperty->getOldProperty())][] = $renameProperty;
}
}

private function renameProperty(ClassLike $classLike, RenameProperty $renameProperty): void
Expand Down Expand Up @@ -136,12 +147,12 @@ private function isUsingTrait(ClassLike $classLike, string $traitName): bool
private function refactorPropertyFetch(
PropertyFetch|StaticPropertyFetch $propertyFetch
): null|PropertyFetch|StaticPropertyFetch {
foreach ($this->renamedProperties as $renamedProperty) {
$oldProperty = $renamedProperty->getOldProperty();
if (! $this->isName($propertyFetch, $oldProperty)) {
continue;
}
$propertyName = $this->getName($propertyFetch);
if ($propertyName === null) {
return null;
}

foreach ($this->renamedPropertiesByOldProperty[strtolower($propertyName)] ?? [] as $renamedProperty) {
$varPropertyFetch = $propertyFetch instanceof PropertyFetch ? $propertyFetch->var : $propertyFetch->class;
if (! $this->isObjectType($varPropertyFetch, $renamedProperty->getObjectType())) {
continue;
Expand Down
20 changes: 13 additions & 7 deletions rules/Renaming/Rector/StaticCall/RenameStaticMethodRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
final class RenameStaticMethodRector extends AbstractRector implements ConfigurableRectorInterface
{
/**
* @var RenameStaticMethod[]
* @var array<string, RenameStaticMethod[]>
*/
private array $staticMethodRenames = [];
private array $staticMethodRenamesByOldMethod = [];

public function getRuleDefinition(): RuleDefinition
{
Expand All @@ -49,11 +49,12 @@ public function getNodeTypes(): array
*/
public function refactor(Node $node): ?Node
{
foreach ($this->staticMethodRenames as $staticMethodRename) {
if (! $this->isName($node->name, $staticMethodRename->getOldMethod())) {
continue;
}
$methodName = $this->getName($node->name);
if ($methodName === null) {
return null;
}

foreach ($this->staticMethodRenamesByOldMethod[strtolower($methodName)] ?? [] as $staticMethodRename) {
if (! $this->isObjectType($node->class, $staticMethodRename->getOldObjectType())) {
continue;
}
Expand All @@ -70,7 +71,12 @@ public function refactor(Node $node): ?Node
public function configure(array $configuration): void
{
Assert::allIsAOf($configuration, RenameStaticMethod::class);
$this->staticMethodRenames = $configuration;

// bucket by lowercased old method name (method names are case-insensitive)
$this->staticMethodRenamesByOldMethod = [];
foreach ($configuration as $staticMethodRename) {
$this->staticMethodRenamesByOldMethod[strtolower($staticMethodRename->getOldMethod())][] = $staticMethodRename;
}
}

private function rename(StaticCall $staticCall, RenameStaticMethod $renameStaticMethod): StaticCall
Expand Down
Loading