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

declare(strict_types=1);

/**
* Benchmark for FuncCallToStaticCallRector (representative of the Transform value-object bucket change).
*
* The Transform rules (FuncCallToStaticCall, FuncCallToNew, ConstFetchToClassConstFetch,
* StaticCallToFuncCall, StaticCallToNew, MethodCallToFuncCall, MethodCallToStaticCall) each held a
* flat list / keyed map and scanned the whole config per visited node, running isName() on every
* entry. 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 function calls whose names are NOT in the map (the common
* case), isolating the scan cost.
*
* Run on both branches to compare:
* php bench/transform_funccall_to_staticcall_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\FuncCall;
use Rector\DependencyInjection\LazyContainerFactory;
use Rector\PhpParser\Node\BetterNodeFinder;
use Rector\Testing\TestingParser\TestingParser;
use Rector\Transform\Rector\FuncCall\FuncCallToStaticCallRector;
use Rector\Transform\ValueObject\FuncCallToStaticCall;

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

const RENAMES = 400; // configured func-call-to-static-call entries
const CALLS = 200; // function 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(FuncCallToStaticCallRector::class);

$funcCallsToStaticCalls = [];
for ($i = 0; $i < RENAMES; ++$i) {
$funcCallsToStaticCalls[] = new FuncCallToStaticCall('old_function_' . $i, 'Bench\\SomeClass' . $i, 'method' . $i);
}

$rector->configure($funcCallsToStaticCalls);

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

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

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

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

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

printf('entries: %d, calls: %d, repeats: %d, refactor() calls: %d%s', RENAMES, count($funcCalls), 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 @@ -20,9 +20,9 @@
final class ConstFetchToClassConstFetchRector extends AbstractRector implements ConfigurableRectorInterface
{
/**
* @var ConstFetchToClassConstFetch[]
* @var array<string, ConstFetchToClassConstFetch[]>
*/
private array $constFetchToClassConsts = [];
private array $constFetchToClassConstsByOldName = [];

public function getRuleDefinition(): RuleDefinition
{
Expand All @@ -43,11 +43,12 @@ public function getNodeTypes(): array
*/
public function refactor(Node $node): ?ClassConstFetch
{
foreach ($this->constFetchToClassConsts as $constFetchToClassConst) {
if (! $this->isName($node, $constFetchToClassConst->getOldConstName())) {
continue;
}
$constName = $this->getName($node);
if ($constName === null) {
return null;
}

foreach ($this->constFetchToClassConstsByOldName[strtolower($constName)] ?? [] as $constFetchToClassConst) {
return $this->nodeFactory->createClassConstFetch(
$constFetchToClassConst->getNewClassName(),
$constFetchToClassConst->getNewConstName()
Expand All @@ -60,6 +61,10 @@ public function refactor(Node $node): ?ClassConstFetch
public function configure(array $configuration): void
{
Assert::allIsAOf($configuration, ConstFetchToClassConstFetch::class);
$this->constFetchToClassConsts = $configuration;

$this->constFetchToClassConstsByOldName = [];
foreach ($configuration as $constFetchToClassConst) {
$this->constFetchToClassConstsByOldName[strtolower($constFetchToClassConst->getOldConstName())][] = $constFetchToClassConst;
}
}
}
20 changes: 13 additions & 7 deletions rules/Transform/Rector/FuncCall/FuncCallToNewRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,17 @@ public function getNodeTypes(): array
*/
public function refactor(Node $node): ?Node
{
foreach ($this->functionToNew as $function => $new) {
if (! $this->isName($node, $function)) {
continue;
}
$funcName = $this->getName($node);
if ($funcName === null) {
return null;
}

return new New_(new FullyQualified($new), $node->args);
$new = $this->functionToNew[strtolower($funcName)] ?? null;
if ($new === null) {
return null;
}

return null;
return new New_(new FullyQualified($new), $node->args);
}

/**
Expand All @@ -86,6 +88,10 @@ public function configure(array $configuration): void
{
Assert::allString($configuration);

$this->functionToNew = $configuration;
// keyed by lowercased function name (function names are case-insensitive)
$this->functionToNew = [];
foreach ($configuration as $function => $new) {
$this->functionToNew[strtolower($function)] = $new;
}
}
}
19 changes: 12 additions & 7 deletions rules/Transform/Rector/FuncCall/FuncCallToStaticCallRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
final class FuncCallToStaticCallRector extends AbstractRector implements ConfigurableRectorInterface
{
/**
* @var FuncCallToStaticCall[]
* @var array<string, FuncCallToStaticCall[]>
*/
private array $funcCallsToStaticCalls = [];
private array $funcCallsToStaticCallsByOldFunc = [];

public function getRuleDefinition(): RuleDefinition
{
Expand All @@ -47,11 +47,12 @@ public function getNodeTypes(): array
*/
public function refactor(Node $node): ?Node
{
foreach ($this->funcCallsToStaticCalls as $funcCallToStaticCall) {
if (! $this->isName($node, $funcCallToStaticCall->getOldFuncName())) {
continue;
}
$funcName = $this->getName($node);
if ($funcName === null) {
return null;
}

foreach ($this->funcCallsToStaticCallsByOldFunc[strtolower($funcName)] ?? [] as $funcCallToStaticCall) {
return $this->nodeFactory->createStaticCall(
$funcCallToStaticCall->getNewClassName(),
$funcCallToStaticCall->getNewMethodName(),
Expand All @@ -69,6 +70,10 @@ public function configure(array $configuration): void
{
Assert::allIsAOf($configuration, FuncCallToStaticCall::class);

$this->funcCallsToStaticCalls = $configuration;
// bucket by lowercased old function name (function names are case-insensitive)
$this->funcCallsToStaticCallsByOldFunc = [];
foreach ($configuration as $funcCallToStaticCall) {
$this->funcCallsToStaticCallsByOldFunc[strtolower($funcCallToStaticCall->getOldFuncName())][] = $funcCallToStaticCall;
}
}
}
19 changes: 12 additions & 7 deletions rules/Transform/Rector/MethodCall/MethodCallToFuncCallRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
final class MethodCallToFuncCallRector extends AbstractRector implements ConfigurableRectorInterface
{
/**
* @var MethodCallToFuncCall[]
* @var array<string, MethodCallToFuncCall[]>
*/
private array $methodCallsToFuncCalls = [];
private array $methodCallsToFuncCallsByMethod = [];

public function getRuleDefinition(): RuleDefinition
{
Expand Down Expand Up @@ -74,11 +74,12 @@ public function refactor(Node $node): ?Node
return null;
}

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

foreach ($this->methodCallsToFuncCallsByMethod[strtolower($methodName)] ?? [] as $methodCallToFuncCall) {
if (! $this->isObjectType($node->var, new ObjectType($methodCallToFuncCall->getObjectType()))) {
continue;
}
Expand All @@ -95,6 +96,10 @@ public function refactor(Node $node): ?Node
public function configure(array $configuration): void
{
Assert::allIsInstanceOf($configuration, MethodCallToFuncCall::class);
$this->methodCallsToFuncCalls = $configuration;

$this->methodCallsToFuncCallsByMethod = [];
foreach ($configuration as $methodCallToFuncCall) {
$this->methodCallsToFuncCallsByMethod[strtolower($methodCallToFuncCall->getMethodName())][] = $methodCallToFuncCall;
}
}
}
18 changes: 11 additions & 7 deletions rules/Transform/Rector/MethodCall/MethodCallToStaticCallRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
final class MethodCallToStaticCallRector extends AbstractRector implements ConfigurableRectorInterface
{
/**
* @var MethodCallToStaticCall[]
* @var array<string, MethodCallToStaticCall[]>
*/
private array $methodCallsToStaticCalls = [];
private array $methodCallsToStaticCallsByOldMethod = [];

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

foreach ($this->methodCallsToStaticCallsByOldMethod[strtolower($methodName)] ?? [] as $methodCallToStaticCall) {
if (! $this->isObjectType($node->var, $methodCallToStaticCall->getOldObjectType())) {
continue;
}
Expand All @@ -105,6 +106,9 @@ public function configure(array $configuration): void
{
Assert::allIsAOf($configuration, MethodCallToStaticCall::class);

$this->methodCallsToStaticCalls = $configuration;
$this->methodCallsToStaticCallsByOldMethod = [];
foreach ($configuration as $methodCallToStaticCall) {
$this->methodCallsToStaticCallsByOldMethod[strtolower($methodCallToStaticCall->getOldMethod())][] = $methodCallToStaticCall;
}
}
}
18 changes: 11 additions & 7 deletions rules/Transform/Rector/StaticCall/StaticCallToFuncCallRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
final class StaticCallToFuncCallRector extends AbstractRector implements ConfigurableRectorInterface
{
/**
* @var StaticCallToFuncCall[]
* @var array<string, StaticCallToFuncCall[]>
*/
private array $staticCallsToFunctions = [];
private array $staticCallsToFunctionsByMethod = [];

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

foreach ($this->staticCallsToFunctionsByMethod[strtolower($methodName)] ?? [] as $staticCallToFunction) {
if (! $this->isObjectType($node->class, $staticCallToFunction->getObjectType())) {
continue;
}
Expand All @@ -71,6 +72,9 @@ public function configure(array $configuration): void
{
Assert::allIsAOf($configuration, StaticCallToFuncCall::class);

$this->staticCallsToFunctions = $configuration;
$this->staticCallsToFunctionsByMethod = [];
foreach ($configuration as $staticCallToFunction) {
$this->staticCallsToFunctionsByMethod[strtolower($staticCallToFunction->getMethod())][] = $staticCallToFunction;
}
}
}
20 changes: 12 additions & 8 deletions rules/Transform/Rector/StaticCall/StaticCallToNewRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
final class StaticCallToNewRector extends AbstractRector implements ConfigurableRectorInterface
{
/**
* @var StaticCallToNew[]
* @var array<string, StaticCallToNew[]>
*/
private array $staticCallsToNews = [];
private array $staticCallsToNewsByMethod = [];

public function getRuleDefinition(): RuleDefinition
{
Expand Down Expand Up @@ -67,12 +67,13 @@ public function getNodeTypes(): array
*/
public function refactor(Node $node): ?Node
{
foreach ($this->staticCallsToNews as $staticCallToNew) {
if (! $this->isName($node->class, $staticCallToNew->getClass())) {
continue;
}
$methodName = $this->getName($node->name);
if ($methodName === null) {
return null;
}

if (! $this->isName($node->name, $staticCallToNew->getMethod())) {
foreach ($this->staticCallsToNewsByMethod[strtolower($methodName)] ?? [] as $staticCallToNew) {
if (! $this->isName($node->class, $staticCallToNew->getClass())) {
continue;
}

Expand All @@ -94,6 +95,9 @@ public function configure(array $configuration): void
{
Assert::allIsAOf($configuration, StaticCallToNew::class);

$this->staticCallsToNews = $configuration;
$this->staticCallsToNewsByMethod = [];
foreach ($configuration as $staticCallToNew) {
$this->staticCallsToNewsByMethod[strtolower($staticCallToNew->getMethod())][] = $staticCallToNew;
}
}
}
Loading