DEV Community

Md Mahbubur Rahman
Md Mahbubur Rahman

Posted on

Why PHP frameworks often (still) perform slower than Python / Go / Rust / Java frameworks

TL;DR (quick summary)

  • Raw microbenchmark results (TechEmpower Web Framework Benchmarks) repeatedly show compiled-oriented stacks (Go, Rust, high-tuned Java/.NET) delivering orders of magnitude higher requests/sec than typical PHP frameworks (Laravel, Symfony) in best-case, in-memory tests. This often looks like 10–30× or more advantage for the fastest Go/Rust/Java frameworks on simple JSON/hello-world tests.
  • PHP has closed the gap significantly with PHP 7/8 (OPcache, JIT, optimization) — real PHP app throughput improved materially (and PHP 8.3 produced measurable additional gains), but the language/runtime and typical framework patterns still favor developer productivity over raw throughput. Expect tens of percent improvement from version upgrades, not orders-of-magnitude parity in microbenchmarks.
  • In real apps (DB I/O, network, templates, caching), language differences often shrink — the dominant cost is I/O and architecture, not pure request CPU — so measured field performance can be much closer than microbenchmarks suggest. “Test your app” still holds.

What the authoritative benchmarks show (and how to read them)

The TechEmpower Framework Benchmarks (TFB) are the standard public comparison for raw framework throughput/latency across many languages and frameworks. TFB runs focused scenarios such as plaintext, JSON serialization, single-query and multiple-query database access, and updates. The winners on pure throughput consistently include highly-tuned frameworks written in Rust, Go, C#, and specialized Java stacks, while typical PHP frameworks sit much lower in raw throughput rankings.

A convenient public synthesis of TFB results shows the common pattern: on the simplest, CPU-bound endpoints (plaintext/JSON), the fastest Go/Rust/Java stacks can achieve 10–30× the requests/second of baseline PHP frameworks in the same environment; in some TFB permutations the top .NET or Go implementations score 20–40× compared to a typical Laravel example. That’s microbenchmark territory — heavily optimized, minimal handlers, and tuned I/O stacks.

Important nuance: TFB is extremely useful to compare language/runtime overhead and framework internals on tiny handlers. It doesn’t necessarily reflect a real site that spends 90% of time in DB queries, remote APIs, or template rendering. Many engineers overextend TFB conclusions to full-stack applications, which is where caution is needed.

Why PHP tends to lag in these microbenchmarks — technical reasons

1. Runtime model: interpreted/VM vs ahead-of-time compiled code

  • Languages like Go produce native binaries with simple, efficient concurrency primitives; Rust compiles to native code that eliminates runtime costs; Java/JVM uses highly-optimized JIT and mature GC tuned for server workloads. PHP historically executes via an interpreter/bytecode VM, with OPcache reducing parse time but still running per-request execution logic unless using long-running process models. That execution model can add CPU overhead per request in synthetic tests.

Implication: native code + low runtime overhead wins in microbenchmarks.

2. Process / threading model and long-running services

  • Go and Rust frameworks commonly run as a single long-running process with evented or lightweight-thread scheduling handling many connections in the same process memory; that reduces context switching, repeated boot costs, memory churn, and allocation/deallocation overhead. Traditional PHP (Zend + FPM) uses a pool of short-lived worker processes where each logical request executes PHP code from start to finish in an isolated process — this model isolates faults and simplifies lifecycle, but it increases per-request overhead relative to a single-binary approach. (Note: modern PHP alternatives — Swoole, RoadRunner — provide long-running models to close this gap.)

Implication: process model matters — long-running evented runtimes are more efficient for microbenchmarks.

3. Autoloading, reflection, and heavy framework abstractions

  • PHP ecosystems rely heavily on Composer autoloaders, heavy middleware stacks, large ORMs (Eloquent, Doctrine) and dependency injection. Those abstractions improve developer productivity but add CPU and memory overhead per request (class resolution, reflection, metadata parsing). In minimal TFB handlers the overhead is proportionally larger; engineered Go/Rust “hello” handlers are literally a few syscalls.

Implication: framework design choices (convenience vs minimalism) directly shape microbenchmark results.

4. Garbage collection and allocation patterns

  • Modern JVMs and Go’s runtime include well-tuned GC/heap strategies designed for long-running server applications. Rust has no runtime GC and enables zero-cost abstractions. PHP’s memory allocator and garbage collection were designed for the classic request lifecycle; while OPcache and improvements have helped, in microbenchmarks the allocation/collection behavior is less optimal than specialized server runtimes.

5. I/O stacks and async support

  • Historically PHP lacked first-class async primitives; while frameworks and libs (ReactPHP, Amp, Swoole) added async/evented capabilities, the mainstream frameworks and libraries are still mostly sync/blocking in style. Go has concurrency primitives in the language; async I/O is idiomatic and lightweight. This means that out of the box, PHP frameworks can be blocked on I/O more often and it’s harder to expose efficient concurrency in a typical app.

Implication: native concurrency support in languages leads to better utilization under concurrency.

What changed: PHP 7/8 and the JIT — improvements and limits

The community saw a major leap with PHP 7 (engine internals, reduced memory usage) and continued improvements with PHP 8+: OPcache, preloading, incremental JIT, and language optimizations cut per-request costs substantially. Vendors and benchmarking articles (Kinsta) have measured significant gains when upgrading minor versions (e.g., PHP 8.3 yields up to ~38% gain over 8.2 for specific Laravel demo workloads). But note that these are version-upgrade gains, not a change in the architectural nature of PHP.

Meta (Facebook) developed HHVM and Hack historically to address PHP’s performance and scale needs; many of HHVM’s ideas influenced later PHP optimizations. For massive proprietary deployments, organizations have built custom compilers/C++ runtimes and extensive platform optimizations — but those are not off-the-shelf PHP framework wins for most teams.

Net effect: PHP is much faster than a decade ago — but the remaining structural differences still show in tight microbenchmarks.

Numbers: realistic percentage comparisons

  1. TechEmpower (microbenchmarks / round results): fastest Go/Rust/.NET frameworks frequently outpace typical PHP frameworks by an order of magnitude or more on plaintext and JSON tests (TechEmpower GitHub).
  2. Version upgrade gains within PHP: upgrading a real app to PHP 8.3 from 8.2 showed ~38% improvement in specific Laravel demos (Kinsta).
  3. Field/real-world caveat: independent WordPress/Drupal benchmarks show app-specific variability — properly tuned PHP setups can deliver comparable latency under realistic loads (Jeff Geerling Dev Blog).
  4. Continuous benchmarking status: TechEmpower’s continuous runner shows coverage across hundreds of implementations (TechEmpower Benchmarks).

Bottom line: in synthetic CPU-bound tests the top Go/Rust/Java frameworks commonly reach 10×–30× the throughput of typical PHP frameworks; in real I/O-bound apps, differences narrow and are often measured in single to low-double-digit percents after architecture and caching are considered.

Practical engineering implications

  • CPU-bound requests: Go/Rust/Java for hot paths.
  • I/O-bound apps: PHP is usually sufficient if tuned.
  • Developer velocity/ecosystem: PHP frameworks excel; consider hybrid microservices for critical paths.

Closing the gap in PHP

  1. Upgrade PHP to 8.x + enable OPcache + preloading.
  2. Use long-running process options (Swoole, RoadRunner).
  3. Offload CPU-heavy endpoints to Go/Rust.
  4. Cache aggressively.
  5. Profile and optimize framework code.
  6. Use FFI/natively compiled extensions for hot functions.
  7. Tune server/platform (network, nginx, FPM pools).

Sources & further reading

Top comments (0)