I generate a lot of UUIDs. Primary keys, cache keys, event IDs, request-trace IDs. Probably too many, if I'm honest about it. On a busy request path the same function gets called dozens of times before the response is even assembled, and across a fleet that adds up to a number of UUIDs per second I would rather not write down.
For years that cost was invisible to me, the way a single random_bytes() call is invisible until you make a few billion of them. Then it showed up in a profile, sitting higher than it had any right to, and I started paying attention to where the time actually went. It went to two places: pulling fresh entropy from the kernel once per UUID, and formatting 16 raw bytes into the 36-character canonical string. Both are cheap. Neither is free. Multiply by "too many" and you get a real slice of CPU spent doing nothing but minting identifiers.
So I wrote fast_uuid, a PHP extension that does UUID generation in pure C. This post is about why the two existing options each left a gap, what...
I've reached for igbinary on nearly every PHP project I've shipped in the last decade. It's smaller and faster than PHP's native serialize(), it's stable, and it has been the obvious default for so long that reaching for it stopped being a decision.
So phpser started as curiosity, not a complaint. igbinary is good. Could a serializer built specifically for cache workloads do better?
I wanted two things from it. It should be fast on the shapes a cache actually holds, where a value is decoded far more often than it's encoded. And it should be safe to decode bytes from a store an attacker might reach, because unserialize() on untrusted input is one of PHP's oldest exploit primitives. igbinary gives you the speed; the safety you bolt on yourself. phpser builds in both.
On the shapes that matter for caches it encodes 10 to 70% faster than igbinary and decodes 12 to 75% faster, with packed numeric data also 65% smaller on the wire. Its signed mode refuses to decode any payload that wasn't produced with yo...
// Before
$payload = json_encode($response);
$data = json_decode($input, true);
$ok = json_validate($input);
// After
$payload = fastjson_encode($response);
$data = fastjson_decode($input, true);
$ok = fastjson_validate($input);
That's the migration. Search-and-replace json_* for fastjson_*. JSON flags, error constants, and last-error semantics carry across byte-for-byte. The two extensions sit next to each other in the same process; adoption is per call site, not per repo.
On the simdjson_php canonical 14.8 MB corpus, that swap buys 6.06× encode throughput, 2.66× decode, and 5.10× validate against ext/json on the same PHP 8.6.0-dev build. The repo is at github.com/iliaal/fastjson. 0.3.0 shipped yesterday.
Why drop in faster JSON for PHP
ext/json is fine. It's correct, well-maintained, and tracks the spec. On low-traffic endpoints it isn't on anyone's profiler. The cost shows up at scale: any application that calls json_encode and json_decode on every request path eventua...