u/edmondifcastle

I've been optimizing a PHP server written in C, here's what makes it fast

The first-of-its-kind WebServer for asynchronous PHP, written in C, where the server itself is deeply integrated with the PHP VM.

The whole request lifecycle (parse, dispatch, respond) happens on a single thread. Same model as NGINX, Node.js, or Rust's Tokio: one thread owns the connection and the request end to end. There is no handoff between an accept thread and a worker thread, no locks, no context switches.

medium.com
u/edmondifcastle — 2 days ago
▲ 66 r/PHP

I've been optimizing a PHP server written in C, here's what makes it fast

TrueAsync 0.7.0 is shipping very soon, with a thread pool and a few other features. But the most interesting part is probably TrueAsync Server: a high-performance HTTP/1.1, HTTP/2 and HTTP/3 server embedded directly into PHP.

Benchmarks: https://www.http-arena.com/leaderboard/

Everything in one thread

The whole request lifecycle (parse, dispatch, respond) happens on a single thread. Same model as NGINX, Node.js, or Rust's Tokio: one thread owns the connection and the request end to end. There is no handoff between an accept thread and a worker thread, no locks, no context switches.

Why C?

It embeds straight into PHP, links against the OpenSSL already in your build, and uses the de-facto-standard C libraries: nghttp2 (HTTP/2), ngtcp2 plus nghttp3 (HTTP/3), llhttp (the same HTTP/1 parser Node.js uses). It runs on the Zend VM, so server memory and PHP memory share one memory_limit.

Multi-protocol, one port

HTTP/1.1, HTTP/2, WebSocket, SSE and gRPC share a single TCP port and event loop (protocol picked via ALPN or HTTP Upgrade). HTTP/3 runs on the same UDP port and gets advertised via Alt-Svc. One $server->start() serves all of them.

The API is two classes

$server = new HttpServer(
    (new HttpServerConfig())->addListener('0.0.0.0', 8080)
);

$server->addHttpHandler(function ($request, $response) {
    $response->setStatusCode(200)->setBody('Hello, World!');
});

$server->start();

Each handler runs in its own coroutine (one per request on H1, one per stream on H2/H3). When a handler awaits a DB query, it blocks nothing else.

Streaming is first-class. $res->send($chunk) pushes data straight onto the wire: Transfer-Encoding: chunked on H1, DATA frames on H2/H3, same handler code either way. Great for SSE, big exports, gRPC. There is even $res->sendable() for per-stream backpressure. Request bodies stream too via $req->readBody(), so you can proxy a multi-GB upload without ever holding it in memory.

Where the speed comes from

Not one big trick, just a pile of small ones:

  • Pooling everywhere. Body buffers, encoders, streams, connection slots. The allocator barely gets touched on repeat requests.
  • Geometric buffer growth. PHP's smart_str has a hidden cliff where every grow becomes a syscall whose cost scales with size. On large bodies that ate up to half the request time.
  • Zero-copy hot paths. Multipart parses in place, sendfile() for large files.
  • A static file handler that never enters the PHP VM at all. Pure C state machine, zero-copy sendfile, built-in MIME table, ETags, range requests, precompressed sidecars. The slowest part of any PHP server is PHP, so for static assets it just skips it.

The whole point is to keep the server invisible relative to the actual workload. It lives between coroutines: while your PHP waits on the database, it is already accepting the next request.

What's next

It's early-stage but you can try it today. WebSocket, gRPC and telemetry are coming over the next few months. The repo is on GitHub under true-async. Happy to answer questions in the comments.

Git: https://github.com/true-async/server
Additional: https://medium.com/@edmond.ht/trueasync-server-e6ed1ae9e8ec

reddit.com
u/edmondifcastle — 2 days ago