Skip to content

[Performance] Dev is slow with thousands of route annotations #49036

@pbowyer

Description

@pbowyer

Symfony version(s) affected

6.2.3

Description

I am working on a large Symfony app with approx 1700 @Route annotations and (so far) 1 #[Route] attribute, across 1350 files.

XDebug is disabled.

bin/console cache:clear takes 24 seconds to run. 13 seconds of this is spent in RouterCacheWarmer.

Almost all this time is spent in \Symfony\Component\Routing\Loader\AnnotationFileLoader::load().

I do not have blackfire so I am limited in the profiling I can do (I have been adding hrtime() around calls to see how long they take).

Why does this matter?

In production it doesn't. In development it's a slowdown in multiple places.

Take a functional test as an example. If I repeatedly run the functional test I pay the cost of the annotations being loaded on the first run. With the fixtures we have, each subsequent run takes ~8 seconds.

If I make any edit to the controller I'm testing, e.g. adding $x = 1; inside the method body, the routes are reloaded and it takes ~13 seconds, so 5 seconds longer, to rerun the test.

There is something more complex going on because this adds 5 seconds whereas on cache:clear it's a consistent 13 seconds. I have not got to the bottom of that.

How to reproduce

Add a lot of routes to a project.

Possible Solution

This project uses DDD/CQRS and subclasses \Symfony\Component\Config\Loader\Loader and for local development I have added a permanent annotation cache outside the Symfony cache directory. This removes the performance drag during development but is not a good solution.

<?php  
declare(strict_types=1);  
  
namespace Example;  
  
use Symfony\Component\Config\Loader\Loader;  
use Symfony\Component\Finder\Finder;  
use Symfony\Component\Finder\SplFileInfo;  
use Symfony\Component\HttpKernel\KernelInterface;  
use Symfony\Component\Routing\RouteCollection;  
  
class DddLoader extends Loader  
{  
    private string $srcDir;  
    public function __construct(KernelInterface $kernel)  
    {  
        $this->srcDir = $kernel->getProjectDir()."/src";  
        // My extra hack
        $this->cacheDir = $kernel->getCacheDir().'/../annotations';  
        @mkdir($this->cacheDir, 0777, true);  
    }  
  
    public function load($resource, string $type = null): RouteCollection  
    {  
        $finder = new Finder();  
        $finder->in($this->srcDir)->name('*Controller.php');  
        $routes = new RouteCollection();
        foreach($finder AS $fileInfo){  
            $hash = md5($fileInfo->getRealPath());  
            if (file_exists($this->cacheDir . "/$hash.php") && filemtime($this->cacheDir . "/$hash.php") > $fileInfo->getMTime()) {  
                $routes->addCollection(unserialize(file_get_contents($this->cacheDir . "/$hash.php")));  
            } else {  
                $annotationLoader = $this->resolver->resolve($fileInfo->getRealPath(), "annotation");    
                if($controllerRoutes = $annotationLoader->load($fileInfo->getRealPath())){  
                    file_put_contents($this->cacheDir . "/$hash.php", serialize($controllerRoutes));  
                    $routes->addCollection($controllerRoutes);  
                }  
            }
        }
        return $routes;  
    }  
  
    public function supports($resource, string $type = null): bool  
    {  
        return ($type == "ddd");  
    }  
}

Short of switching from annotations to another format, which I do not have the power to do, are there any improvements that can be made to Symfony to improve performance?

Additional Context

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions