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/remove_method_call_param_bucket_bench.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

declare(strict_types=1);

/**
* Benchmark for RemoveMethodCallParamRector (representative of the Arguments/Removing bucket change).
*
* RemoveMethodCallParamRector / ArgumentAdderRector / ArgumentRemoverRector / RemoveFuncCallRector
* each scanned their whole configured list per visited node, running isName() on each entry. The
* change buckets the list by lowercased old method name once in configure() (RemoveFuncCallRector
* flips to a lowercased name set), so a node only visits entries sharing its name.
*
* This bench drives refactor() over many method calls whose names are NOT in the map (the common
* case), isolating the scan cost.
*
* Run on both branches to compare:
* php bench/remove_method_call_param_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\MethodCall;
use Rector\Arguments\Rector\MethodCall\RemoveMethodCallParamRector;
use Rector\Arguments\ValueObject\RemoveMethodCallParam;
use Rector\DependencyInjection\LazyContainerFactory;
use Rector\PhpParser\Node\BetterNodeFinder;
use Rector\Testing\TestingParser\TestingParser;

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

const ENTRIES = 400; // configured remove-method-call-param entries
const CALLS = 200; // method 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(RemoveMethodCallParamRector::class);

$removeMethodCallParams = [];
for ($i = 0; $i < ENTRIES; ++$i) {
$removeMethodCallParams[] = new RemoveMethodCallParam('Bench\\SomeClass' . $i, 'oldMethod' . $i, 0);
}

$rector->configure($removeMethodCallParams);

$source = "<?php\n\nnamespace Bench;\n\n\$value = new Other();\n";
for ($i = 0; $i < CALLS; ++$i) {
// method names deliberately absent from the configured entries
$source .= "\$value->freshCall{$i}(1, 2);\n";
}

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

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

/** @var MethodCall[] $methodCalls */
$methodCalls = $betterNodeFinder->findInstanceOf($stmts, MethodCall::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, $methodCalls): void {
foreach ($methodCalls as $methodCall) {
$rector->refactor($methodCall);
}
};

// 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($methodCalls);

printf('entries: %d, calls: %d, repeats: %d, refactor() calls: %d%s', ENTRIES, count($methodCalls), 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);
25 changes: 13 additions & 12 deletions rules/Arguments/Rector/ClassMethod/ArgumentAdderRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@
final class ArgumentAdderRector extends AbstractRector implements ConfigurableRectorInterface
{
/**
* @var ArgumentAdder[]|ArgumentAdderWithoutDefaultValue[]
* @var array<string, array<ArgumentAdder|ArgumentAdderWithoutDefaultValue>>
*/
private array $addedArguments = [];
private array $addedArgumentsByMethod = [];

private bool $hasChanged = false;

Expand Down Expand Up @@ -136,7 +136,11 @@ public function refactor(Node $node): MethodCall|StaticCall|Class_|null
public function configure(array $configuration): void
{
Assert::allIsAnyOf($configuration, [ArgumentAdder::class, ArgumentAdderWithoutDefaultValue::class]);
$this->addedArguments = $configuration;

$this->addedArgumentsByMethod = [];
foreach ($configuration as $addedArgument) {
$this->addedArgumentsByMethod[strtolower($addedArgument->getMethod())][] = $addedArgument;
}
}

private function isObjectTypeMatch(MethodCall|StaticCall $call, ObjectType $objectType): bool
Expand Down Expand Up @@ -380,11 +384,7 @@ private function refactorCall(StaticCall|MethodCall $call): void
return;
}

foreach ($this->addedArguments as $addedArgument) {
if (! $this->nodeNameResolver->isStringName($callName, $addedArgument->getMethod())) {
continue;
}

foreach ($this->addedArgumentsByMethod[strtolower($callName)] ?? [] as $addedArgument) {
if (! $this->isObjectTypeMatch($call, $addedArgument->getObjectType())) {
continue;
}
Expand All @@ -395,11 +395,12 @@ private function refactorCall(StaticCall|MethodCall $call): void

private function refactorClassMethod(Class_ $class, ClassMethod $classMethod): void
{
foreach ($this->addedArguments as $addedArgument) {
if (! $this->isName($classMethod, $addedArgument->getMethod())) {
continue;
}
$methodName = $this->getName($classMethod);
if ($methodName === null) {
return;
}

foreach ($this->addedArgumentsByMethod[strtolower($methodName)] ?? [] as $addedArgument) {
if (! $this->isObjectType($class, $addedArgument->getObjectType())) {
continue;
}
Expand Down
19 changes: 12 additions & 7 deletions rules/Arguments/Rector/MethodCall/RemoveMethodCallParamRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
final class RemoveMethodCallParamRector extends AbstractRector implements ConfigurableRectorInterface
{
/**
* @var RemoveMethodCallParam[]
* @var array<string, RemoveMethodCallParam[]>
*/
private array $removeMethodCallParams = [];
private array $removeMethodCallParamsByMethod = [];

public function __construct(
private readonly ReflectionResolver $reflectionResolver,
Expand Down Expand Up @@ -84,11 +84,12 @@ public function refactor(Node $node): ?Node
return null;
}

foreach ($this->removeMethodCallParams as $removeMethodCallParam) {
if (! $this->isName($node->name, $removeMethodCallParam->getMethodName())) {
continue;
}
$methodName = $this->getName($node->name);
if ($methodName === null) {
return null;
}

foreach ($this->removeMethodCallParamsByMethod[strtolower($methodName)] ?? [] as $removeMethodCallParam) {
if (! $this->isCallerObjectType($node, $removeMethodCallParam)) {
continue;
}
Expand Down Expand Up @@ -147,7 +148,11 @@ public function refactor(Node $node): ?Node
public function configure(array $configuration): void
{
Assert::allIsInstanceOf($configuration, RemoveMethodCallParam::class);
$this->removeMethodCallParams = $configuration;

$this->removeMethodCallParamsByMethod = [];
foreach ($configuration as $removeMethodCallParam) {
$this->removeMethodCallParamsByMethod[strtolower($removeMethodCallParam->getMethodName())][] = $removeMethodCallParam;
}
}

private function isCallerObjectType(StaticCall|MethodCall $call, RemoveMethodCallParam $removeMethodCallParam): bool
Expand Down
19 changes: 12 additions & 7 deletions rules/Removing/Rector/ClassMethod/ArgumentRemoverRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@
final class ArgumentRemoverRector extends AbstractRector implements ConfigurableRectorInterface
{
/**
* @var ArgumentRemover[]
* @var array<string, ArgumentRemover[]>
*/
private array $removedArguments = [];
private array $removedArgumentsByMethod = [];

private bool $hasChanged = false;

Expand Down Expand Up @@ -73,11 +73,12 @@ public function refactor(Node $node): MethodCall|StaticCall|ClassMethod|null
{
$this->hasChanged = false;

foreach ($this->removedArguments as $removedArgument) {
if (! $this->isName($node->name, $removedArgument->getMethod())) {
continue;
}
$methodName = $this->getName($node->name);
if ($methodName === null) {
return null;
}

foreach ($this->removedArgumentsByMethod[strtolower($methodName)] ?? [] as $removedArgument) {
if (! $this->nodeTypeResolver->isMethodStaticCallOrClassMethodObjectType(
$node,
$removedArgument->getObjectType()
Expand All @@ -101,7 +102,11 @@ public function refactor(Node $node): MethodCall|StaticCall|ClassMethod|null
public function configure(array $configuration): void
{
Assert::allIsAOf($configuration, ArgumentRemover::class);
$this->removedArguments = $configuration;

$this->removedArgumentsByMethod = [];
foreach ($configuration as $removedArgument) {
$this->removedArgumentsByMethod[strtolower($removedArgument->getMethod())][] = $removedArgument;
}
}

private function processPosition(
Expand Down
24 changes: 15 additions & 9 deletions rules/Removing/Rector/FuncCall/RemoveFuncCallRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@
final class RemoveFuncCallRector extends AbstractRector implements ConfigurableRectorInterface
{
/**
* @var string[]
* keyed by lowercased function name (function names are case-insensitive)
* @var array<string, true>
*/
private array $removedFunctions = [];
private array $removedFunctionNames = [];

public function getRuleDefinition(): RuleDefinition
{
Expand Down Expand Up @@ -60,15 +61,16 @@ public function refactor(Node $node): ?int
return null;
}

foreach ($this->removedFunctions as $removedFunction) {
if (! $this->isName($expr->name, $removedFunction)) {
continue;
}
$funcName = $this->getName($expr->name);
if ($funcName === null) {
return null;
}

return NodeVisitor::REMOVE_NODE;
if (! isset($this->removedFunctionNames[strtolower($funcName)])) {
return null;
}

return null;
return NodeVisitor::REMOVE_NODE;
}

/**
Expand All @@ -77,6 +79,10 @@ public function refactor(Node $node): ?int
public function configure(array $configuration): void
{
Assert::allString($configuration);
$this->removedFunctions = $configuration;

$this->removedFunctionNames = [];
foreach ($configuration as $removedFunction) {
$this->removedFunctionNames[strtolower($removedFunction)] = true;
}
}
}
Loading