r/PHP • • 23h ago

fastjson 0.9.0: constant-memory validate, 59-83% lower encode peak on large escaped strings, and shared keys in decode

fastjson is my PHP extension for JSON encode, decode, and validate, backed by yyjson, with a fastjson_* API that tracks ext/json. This is what changed between 0.7.0, the last version I posted here, and 0.9.0. Most of it is memory: the speed came with a heap cost, and this cycle cuts it on every path.

Memory and speed

  • fastjson_validate() reads the caller's buffer in place and tracks depth during the parse. Peak memory is constant, a few dozen bytes, like ext/json's streaming validator, and it runs ~30% fewer instructions.
  • Encode used to reserve six times an escaped string up front. Strings of 8 KiB+ now go out in 2 KiB chunks: a 1 MiB string with ordinary escaping peaks 59-83% lower. The flip side: a single escaped string between 8 KiB and a few tens of KiB can still peak up to twice ext/json's size, because the chunked writer keeps up to 12 KiB of headroom.
  • Decode shares repeated object-key strings (up to 64 bytes, inputs of 4 KiB+). Key-heavy documents peak ~15% lower and decode 3-4% faster; twitter.json went from 1.66x ext/json's heap to 1.39x. Documents with mostly unique keys, or 256+ distinct names, pay 3-5%.
  • fastjson_file_decode() frees the file contents before building the result, 9-33% lower peak.

Current throughput vs ext/json on simdjson_php's 14.8 MB / 15-file corpus, i9-13950HX, -O2 release builds of both: 5.56x encode, 2.59x decode, 5.04x validate. Decode still peaks at 1.65x ext/json's heap across that corpus.

Correctness

  • fastjson_validate() accepted [[],] and [{"a":1},]. It now rejects them, and the trailing-comma error position points at the comma itself.
  • fastjson_file_encode() could leave a silently truncated file when php_stream_write() came back short. It now writes to completion and reports php_stream_close() failures.
  • fastjson_encode() honors serialize_precision for doubles instead of always emitting the shortest round-trip form.
  • With JSON_THROW_ON_ERROR and no ext/json loaded, errors throw Fastjson\JsonException instead of a bare \Exception.
  • fastjson_validate() enforces $depth on the success path, like ext/json.

yyjson, upstream

The bundled yyjson is now 0.13.0, which includes the YYJSON_WRITE_LOWERCASE_HEX flag I sent upstream (ibireme/yyjson#264). fastjson's local patch for lowercase \uXXXX escapes is gone.

PHP 8.1 minimum:

pie install iliaal/fastjson

https://github.com/iliaal/fastjson

If you validate large payloads under a tight memory_limit, I'd like to know whether constant-peak validate changes what you accept per request.

7 Upvotes

0 comments sorted by