Lift v1.3.0

Benchmarks

Real numbers. Same machine, same PHP build, same handler — three endpoints per framework, hit with ~100 000 requests each via wrk.

PHP 8.3.6 · OPcache + tracing JIT · 64 MB JIT buffer · php -S (single-process) · wrk -t4 -c64 -d30s · recorded 2026-05-15

GET /ping

# Framework req/s p99 ms
1 raw-php 9,009 13.15
2 flight 3,923 21.87
3 lift 3,437 24.38
4 slim 1,727 48.21

GET /json

# Framework req/s p99 ms
1 raw-php 9,030 10.16
2 flight 3,833 22.13
3 lift 3,552 23.91
4 slim 1,759 47.64

GET /users/{id}

# Framework req/s p99 ms
1 raw-php 8,947 10.10
2 flight 3,679 23.04
3 lift 3,375 25.62
4 slim 1,767 44.51
~1.6M
total requests fired
+97%
Lift faster than Slim 4
(both PSR-7 compliant)
24 ms
p99 latency @ 64 concurrent
routing + DI + PSR-7

Reading the results

raw-php is the baseline — a plain if/elseif router with no dependencies. Flight is fast because it bypasses PSR-7 entirely (custom request/response objects, no interfaces). Lift and Slim are both fully PSR-7 · PSR-11 · PSR-15 compliant; Lift is ~97 % faster than Slim 4 in that category.

The ~9 % gap between Flight and Lift is the measurable cost of PSR-7 immutability + the DI container — a worthwhile trade-off for middleware pipelines, testability, and interoperability with the PSR ecosystem.

Methodology

  • All frameworks run as a single php -S process — identical, isolated, single-process conditions.
  • OPcache enabled with tracing JIT and 64 MB buffer (-d opcache.enable_cli=1 -d opcache.jit=tracing -d opcache.jit_buffer_size=64M).
  • Three endpoints with identical semantics: GET /ping (text/plain), GET /json (5-key object), GET /users/{id} (dynamic route + param extraction).
  • Load tool: wrk -t4 -c64 -d30s --latency — 4 threads, 64 concurrent connections, 30 seconds per run. Each endpoint: ~100 000+ requests. Total: ~1.6M requests across all frameworks.
  • Latency percentiles (p90, p99) from wrk's built-in --latency histogram. avg is the mean over the full run.
  • Absolute numbers depend on host CPU. The relative ordering and ratios are the meaningful signal.

Reproduce it yourself

Two files and one command — no extra tools beyond PHP and Composer.

1 · bootstrap

shell
mkdir lift-bench && cd lift-bench
composer require malinichevvv/lift-php
mkdir public

2 · create public/index.php

public/index.php
<?php
require '../vendor/autoload.php';

$app = new \Lift\App();

$app->get('/ping', fn() => 'pong');

$app->get('/json', fn() => [
    'status' => 'ok', 'ts' => microtime(true),
    'memory' => memory_get_usage(), 'php' => PHP_VERSION, 'fw' => 'lift',
]);

$app->get('/users/{id:\d+}', fn(\Lift\Http\Request $r) => [
    'id' => (int) $r->param('id'), 'name' => 'Alice', 'email' => '[email protected]',
]);

$app->run();

3 · start the server (OPcache + JIT)

shell
php -d opcache.enable_cli=1 \
    -d opcache.jit_buffer_size=64M \
    -d opcache.jit=tracing \
    -S 127.0.0.1:8000 -t public

4 · load test with wrk

shell
wrk -t4 -c64 -d30s --latency http://127.0.0.1:8000/ping
wrk -t4 -c64 -d30s --latency http://127.0.0.1:8000/json
wrk -t4 -c64 -d30s --latency http://127.0.0.1:8000/users/42