Skip to content

Benchmark: reflection vs a serialized injector vs CompiledInjector on a large real-world app #135

Description

@koriym

benchmark/di_benchmark.php here uses a small fixture. To show how the three strategies scale, these are numbers from a large production BEAR.Sunday application (~600 compiled DI scripts). The application name is withheld.

Setup: PHP 8.3, OPcache on, Xdebug off. Root = the application root (AppInterface), built once per process (php-fpm shared-nothing — each request is a fresh process).

  • reflectionRay\Di\Injector (builds the Container from the module every process)
  • serialize — a serialize()d injector, unserialize()d per process
  • compiledRay\Compiler\CompiledInjector

Per-process cost to acquire the application root (≈ one php-fpm request, cold):

strategy cost breakdown
reflection ~0.4–0.6 s Container build (annotation reading + binding analysis + AOP weaving) dominates
serialize ~29 ms unserialize() of the whole Container (~25 ms, mostly class autoload) + build (~4 ms); blob ~0.5 MB
compiled ~5 ms lazy require of only the scripts the root touches (~30 of ~600); offline compile ~0.5 s

Takeaways

  • reflection rebuilds the entire Container every process — untenable for shared-nothing. This is the cost the other two exist to avoid.
  • serialize's runtime cost is essentially unserialize() of the full container, so it scales ~linearly with the binding set (and the blob can't live in shared OPcache — it is re-unserialize()d per process).
  • compiled lazily loads only what a request needs (sub-linear), and its scripts can be preloaded into shared OPcache across workers.

In a warm worker, unserialize() drops to ~1–2 ms (classes already loaded), so per-request serialize and compiled both land in the low-millisecond range; the dramatic gap is the cold first request and the linear-vs-sub-linear scaling.

Caveats: indicative single-run figures on one machine (macOS); the numbers above are cold / first-build (the figure that matters for php-fpm per-request). Warm steady-state at this scale was not cleanly isolated (validating OPcache for the full script set was hard), so treat those as approximate. The repo's benchmark/di_benchmark.php reproduces a small, OPcache-validated version of this comparison.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions