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

declare(strict_types=1);

/**
* Benchmark for AddPropertyTypeDeclarationRector (representative of the Add*Type bucket change).
*
* AddParamTypeDeclarationRector / AddPropertyTypeDeclarationRector / AddReturnTypeDeclarationRector
* each scanned their whole configured list per visited node, running isName()/isStringName() on
* each entry before the class/object-type check. The change buckets the list by lowercased old
* name once in configure(), so a node only visits entries sharing its name.
*
* This bench drives refactor() over many untyped properties whose names are NOT in the map (the
* common case), isolating the scan cost.
*
* Run on both branches to compare:
* php bench/add_property_type_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\Stmt\Property;
use PHPStan\Type\StringType;
use Rector\DependencyInjection\LazyContainerFactory;
use Rector\PhpParser\Node\BetterNodeFinder;
use Rector\Testing\TestingParser\TestingParser;
use Rector\TypeDeclaration\Rector\Property\AddPropertyTypeDeclarationRector;
use Rector\TypeDeclaration\ValueObject\AddPropertyTypeDeclaration;

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

const ENTRIES = 400; // configured add-property-type entries
const PROPERTIES = 150; // untyped properties 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(AddPropertyTypeDeclarationRector::class);

$addPropertyTypeDeclarations = [];
for ($i = 0; $i < ENTRIES; ++$i) {
$addPropertyTypeDeclarations[] = new AddPropertyTypeDeclaration('Bench\\SomeParent' . $i, 'oldProperty' . $i, new StringType());
}

$rector->configure($addPropertyTypeDeclarations);

$source = "<?php\n\nnamespace Bench;\n\nclass SomeClass\n{\n";
for ($i = 0; $i < PROPERTIES; ++$i) {
// property names deliberately absent from the configured entries
$source .= " public \$freshProperty{$i};\n";
}

$source .= "}\n";

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

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

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

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

printf('entries: %d, properties: %d, repeats: %d, refactor() calls: %d%s', ENTRIES, count($properties), 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 @@ -31,9 +31,9 @@
final class AddParamTypeDeclarationRector extends AbstractRector implements ConfigurableRectorInterface
{
/**
* @var AddParamTypeDeclaration[]
* @var array<string, AddParamTypeDeclaration[]>
*/
private array $addParamTypeDeclarations = [];
private array $addParamTypeDeclarationsByMethod = [];

private bool $hasChanged = false;

Expand Down Expand Up @@ -103,11 +103,11 @@ public function refactor(Node $node): ?Node
}

$methodName = $this->getName($classMethod);
foreach ($this->addParamTypeDeclarations as $addParamTypeDeclaration) {
if (! $this->nodeNameResolver->isStringName($methodName, $addParamTypeDeclaration->getMethodName())) {
continue;
}
if ($methodName === null) {
continue;
}

foreach ($this->addParamTypeDeclarationsByMethod[strtolower($methodName)] ?? [] as $addParamTypeDeclaration) {
if (! $this->isObjectType($node, $addParamTypeDeclaration->getObjectType())) {
continue;
}
Expand All @@ -130,7 +130,10 @@ public function configure(array $configuration): void
{
Assert::allIsAOf($configuration, AddParamTypeDeclaration::class);

$this->addParamTypeDeclarations = $configuration;
$this->addParamTypeDeclarationsByMethod = [];
foreach ($configuration as $addParamTypeDeclaration) {
$this->addParamTypeDeclarationsByMethod[strtolower($addParamTypeDeclaration->getMethodName())][] = $addParamTypeDeclaration;
}
}

private function shouldSkip(Class_|Interface_ $classLike, ClassMethod $classMethod): bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@
final class AddReturnTypeDeclarationRector extends AbstractRector implements ConfigurableRectorInterface
{
/**
* @var AddReturnTypeDeclaration[]
* @var array<string, AddReturnTypeDeclaration[]>
*/
private array $methodReturnTypes = [];
private array $methodReturnTypesByMethod = [];

private bool $hasChanged = false;

Expand Down Expand Up @@ -94,24 +94,15 @@ public function refactor(Node $node): ?Node
return null;
}

$classMethodNames = [];
foreach ($node->getMethods() as $classMethod) {
$classMethodNames[strtolower((string) $this->getName($classMethod))] = true;
}

foreach ($this->methodReturnTypes as $methodReturnType) {
// cheap name check before the expensive type resolution below
if (! isset($classMethodNames[strtolower($methodReturnType->getMethod())])) {
continue;
}

$objectType = $methodReturnType->getObjectType();
if (! $this->isObjectType($node, $objectType)) {
$methodName = $this->getName($classMethod);
if ($methodName === null) {
continue;
}

foreach ($node->getMethods() as $classMethod) {
if (! $this->isName($classMethod, $methodReturnType->getMethod())) {
foreach ($this->methodReturnTypesByMethod[strtolower($methodName)] ?? [] as $methodReturnType) {
$objectType = $methodReturnType->getObjectType();
if (! $this->isObjectType($node, $objectType)) {
continue;
}

Expand All @@ -138,7 +129,10 @@ public function configure(array $configuration): void
{
Assert::allIsAOf($configuration, AddReturnTypeDeclaration::class);

$this->methodReturnTypes = $configuration;
$this->methodReturnTypesByMethod = [];
foreach ($configuration as $methodReturnType) {
$this->methodReturnTypesByMethod[strtolower($methodReturnType->getMethod())][] = $methodReturnType;
}
}

private function processClassMethodNodeWithTypehints(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@
final class AddPropertyTypeDeclarationRector extends AbstractRector implements ConfigurableRectorInterface
{
/**
* @var AddPropertyTypeDeclaration[]
* @var array<string, AddPropertyTypeDeclaration[]>
*/
private array $addPropertyTypeDeclarations = [];
private array $addPropertyTypeDeclarationsByProperty = [];

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

foreach ($this->addPropertyTypeDeclarations as $addPropertyTypeDeclaration) {
if (! $this->isName($node, $addPropertyTypeDeclaration->getPropertyName())) {
continue;
}
$propertyName = $this->getName($node);
if ($propertyName === null) {
return null;
}

foreach ($this->addPropertyTypeDeclarationsByProperty[strtolower($propertyName)] ?? [] as $addPropertyTypeDeclaration) {
if (! $this->isClassReflectionType($classReflection, $addPropertyTypeDeclaration->getClass())) {
continue;
}
Expand All @@ -113,7 +114,11 @@ public function refactor(Node $node): ?Node
public function configure(array $configuration): void
{
Assert::allIsAOf($configuration, AddPropertyTypeDeclaration::class);
$this->addPropertyTypeDeclarations = $configuration;

$this->addPropertyTypeDeclarationsByProperty = [];
foreach ($configuration as $addPropertyTypeDeclaration) {
$this->addPropertyTypeDeclarationsByProperty[strtolower($addPropertyTypeDeclaration->getPropertyName())][] = $addPropertyTypeDeclaration;
}
}

private function isClassReflectionType(ClassReflection $classReflection, string $type): bool
Expand Down
Loading