Skip to content

Even faster JSON#1104

Open
Nicell wants to merge 3 commits into
luau-lang:primaryfrom
Nicell:even-faster-json
Open

Even faster JSON#1104
Nicell wants to merge 3 commits into
luau-lang:primaryfrom
Nicell:even-faster-json

Conversation

@Nicell
Copy link
Copy Markdown
Collaborator

@Nicell Nicell commented May 16, 2026

Optimizes @std/json serialization/deserialization by reducing repeated string and parsing work in hot paths:

  • Adds a per-serialize-call cache for escaped object keys
  • Caches hot serializer buffer/cursor locals in string write paths
  • Rewrites number parsing to a byte-scanning path with a fast integer case
  • Removes redundant whitespace scans in object parsing
  • Caps cached keys at 64 bytes to avoid retaining oversized key strings
Dataset Operation Before After Speedup
tools/dataset.json Deserialize 33.42 MB/s 36.86 MB/s 1.10x
tools/dataset.json Serialize 31.39 MB/s 54.67 MB/s 1.74x
tools/dataset-large.json Deserialize 27.15 MB/s 31.34 MB/s 1.15x
tools/dataset-large.json Serialize 31.80 MB/s 54.59 MB/s 1.72x

Deserialization improved by roughly 10-15%, serialization by roughly 72-74%.

I didn't commit the json bench but may add it on, just didn't want to make the diff look huge with JSON lol.

@nwinn-student
Copy link
Copy Markdown

For serializeAny:

You could cache the type of the value to avoid having to call it many times in worst-case scenerios. table is already at 4 calls and string is at 3.

There is a low likelihood of json.null being used enough to outweight the performance benefits this slight change brings. An early return would ensure json.null doesn't slow down (or if it does, only marginally).

@Nicell
Copy link
Copy Markdown
Collaborator Author

Nicell commented May 17, 2026

You could cache the type of the value to avoid having to call it many times in worst-case scenerios. table is already at 4 calls and string is at 3.

Locally I'm seeing equal or worse performance with this change. I've tried it before and saw similar results. typeof is already a FASTCALL and really cheap. As to why it seems slightly worse even? I'm guessing it's because the typeof local takes up an extra register, but I'm not quite sure.

After this PR I'm hitting quite a wall. There's some inlining opportunities, but I'd rather avoid making the code too ugly. I think potentially some conditionals could be reordered, but that'd also be workload dependent. After that I think wins will have to be on the Luau side, or we'd have to move to a native module like simdjson/yyjson, but that seems unlikely to happen for now.

@Nicell Nicell marked this pull request as ready for review May 17, 2026 16:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants