Autoloading Performance – Avoid These 5 Mistakes!

Autoloading performance affects every PHP application. And no, it’s not a solved problem just because Composer handles autoloading for you nowadays.

In fact, for large applications like Magento, Shopware, or those based on Symfony or Laravel, autoloading can turn into a major performance bottleneck. More than 100 ms per request is not uncommon, caused by one of these common mistakes:

  1. Not dumping a class map
  2. Loading unused classes in every request
  3. Expensive prepended custom autoloaders
  4. Building your own preloading scripts
  5. Not using OPcache preloading

Let’s break it down.

What Is Autoloading Performance?Autoloading performance is distinct from PHP’s compile performance and interacts closely with OPcache.

When PHP encounters a class that hasn’t yet been loaded, it triggers the autoloader system. This involves calling callbacks registered via spl_autoload_register(). These callbacks are executed in sequence until the class is found or PHP throws a fatal error.

Each time this happens, PHP spends time:

  1. Running the autoloader callback(s)
  2. Calling the constructor of classes
  3. Compiling the class (unless already cached in OPcache)

In this blog post we are going to look at point 1: time spent in autoloader callback(s). And while Composer is responsible for this in modern PHP applications, there is still a lot that can go wrong.

As for point 3: Time spent compiling can be reduced to effectively zero by correctly using OPcache as detailed in this post on our blog.

1. Not Dumping a Class Map

Using classmap-based autoloading can significantly reduce overhead.

With Composer, you can optimize autoloading using:

composer dump-autoload --optimize

This generates a autoload_classmap.php file with a precomputed map of class names to paths. The autoloading code then has a shortcut that greatly reduces the runtime of autoloading.

Without a classmap, Composer must run much more complicated code to compute the path for every class and, most importantly, use an expensive file_exists check.

While it seems like an obvious optimization that is well-known, we still see 10-20% of customers are not optimizing autoloaders for production apps, and it would usually cost you 10-50ms in every request, and sometimes even more.

Tip: Always dump your classmap after potentially generating classes in a compile step. This is especially important in frameworks like Magento that auto-generate hundreds of classes.

2. Loading Unused Classes

Autoloading everything on every request is wasteful.

This happens often due to:

  • Eager Dependency Injection
  • Event systems loading all listeners
  • Route matchers loading every controller

Solution: Introduce lazy-loading or cutoffs to prevent loading unnecessary parts of the object graph.

Symfony framework is a good example where this work has happened under the hood over the last major versions. It’s EventDispatcher includes several optimizations where you don’t have to instantiate all listener objects for all events in every request. It does this by passing a factory closure as a listener instead:

With potentially hundreds of listeners and each of them having many dependencies, this can prevent a lot of autoloading happening for classes that ultimately will not be used in the concrete request.

Bonus: PHP 8.4 introduces native lazy-loading support for DI containers, which will greatly improve this situation in the future. See my YouTube video series on Native Lazy Objects in Doctrine (part 1), Symfony (part 2), and Magento 2 (part 3).

3. Expensive Prepended Custom Autoloaders

A chain of multiple autoloaders can be tricky for performance. Composer’s autoloader should ideally be first in the chain—it resolves ~95% of classes quickly with the generated classmap. Then more custom and specialized, potentially expensive autoloaders can run afterward.

Custom autoloaders that prepend themselves to Composer can slow everything down.

Case in point: the older, now abandoned Laminas package laminas-zendframework-bridge prepended an autoloader before Composer’s autoloader to simplify the ZendFramework to Laminas Rename Migration. That package significantly increased autoload times and automatically activated itself as a second-level dependency when using any kind of Laminas package.

Use spl_autoload_functions() to inspect, deactivate, replace, or reorder autoloaders if needed.

4. Building Your Own Preloading Scripts

Preloading libraries manually was popular before Composer matured. Think: Doctrine 1’s “All-in-One” file or Symfony 2’s early class loaders.

But today, these preloading scripts are largely unnecessary and often counterproductive:

  • They load too many classes upfront, requiring memory, which costs performance.
  • They clutter stack traces and make debugging with XDebug harder
  • They reduce flexibility

Modern DI containers like Symfony’s use smarter preloading based on actual runtime needs.

5. Not Using OPcache Preloading

OPcache preloading is a powerful built-in feature since PHP 7.4.

It allows loading frequently-used classes once at PHP startup, keeping them in memory across all requests.

Benefits:

  • Skip autoload callbacks entirely
  • Reduce file I/O
  • Improve response times by 5–10% for fast endpoints

For high-performance apps, OPcache preloading gives an additional edge that you don’t want to leave on the table. See my previous blog post on OPcache preloading for details.

Conclusion

Composer is a fantastic tool, but it doesn’t prevent autoloading from becoming a performance drag.

Avoiding the five mistakes above can save tens of milliseconds per request, which adds up fast.

Want more PHP performance tips? Subscribe to our newsletter or check out the full video for a deep dive with real-world examples.

Stay fast!

What’s next?

  1. Sign Up for our Newsletter if you don’t want to miss the next post on our blog.
  2. Start your 14 days free trial of Tideways for effortless performance insights into your PHP application.