perf(json): write escapes and indentation directly into the builder - #3820
Draft
bobzhang wants to merge 1 commit into
Draft
perf(json): write escapes and indentation directly into the builder#3820bobzhang wants to merge 1 commit into
bobzhang wants to merge 1 commit into
Conversation
Json::stringify allocated a throwaway String for every string value and object key: escape() built a fresh StringBuilder and returned a String that was immediately copied into the outer buffer, with no fast path for strings needing no escapes. Replace it with escape_into(), which scans for the first escapable code unit (all-ASCII set, so code-unit scanning is exact even around surrogates) and bulk-copies the whole string when clean — the common case for keys and most values; strings containing escapes keep the original char-by-char loop, now writing into the outer builder, which also preserves the old behavior for malformed UTF-16 (unpaired surrogates copy through verbatim; a regression test pins this). indent_str similarly allocated "\n" + " ".repeat(n) per separator for indents past its 8-space cache; write_indent() now writes the newline and spaces directly, allocation-free at any depth. Non-positive indent now means compact output (previously a negative indent aborted inside String::repeat). Adds stringify benchmarks (matching parse_bench_test.mbt style). Native release, before -> after: - string-heavy n=1000: 3.89 ms -> 1.16 ms (~3.4x) - pretty indent=4 n=200: 1.11 ms -> 0.37 ms (~3.0x) - escape-heavy n=500: 320 us -> 220 us (~1.5x) Reviewed by Codex CLI (codex-cli 0.144.1) in two rounds: the first flagged an abort on unpaired surrogates after escape chars and a negative-indent behavior change; both were fixed and re-review approved: "Both concerns are resolved, and well-formed output remains equivalent... Public interface is unchanged." Signed-off-by: Codex CLI <codex@openai.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Collaborator
Coverage Report for CI Build 5182Coverage decreased (-0.009%) to 91.259%Details
Uncovered ChangesNo uncovered changes found. Coverage Regressions1 previously-covered line in 1 file lost coverage.
Coverage Stats
💛 - Coveralls |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Json::stringifyallocated a throwawayStringfor every string value and object key:escape()built a freshStringBuilder, returned aString, and the caller copied it into the outer buffer — with no fast path for the overwhelmingly common case of strings that need no escaping.Change
escape_into(buf, str, escape_slash~)replacesescape(): scans code units for the first escapable character (the escape set is all-ASCII, so code-unit scanning is exact even around surrogate pairs). Clean strings — most keys and values — become a singlewrite_string. Strings containing escapes take the original char-by-char loop, now writing directly into the outer builder. The fallback path is byte-identical to the old loop, including for malformed UTF-16 (unpaired surrogates copy through verbatim; a regression test built withunsafe_substringpins both paths).write_indentreplacesindent_str, writing the newline and spaces directly instead of allocating"\n" + " ".repeat(n)per separator beyond the old 8-space cache. Non-positiveindentnow explicitly means compact output (previously a negative indent hit an accidental abort insideString::repeat).Benchmarks (native, release; committed as
json/stringify_bench_test.mbt)Review
Reviewed by Codex CLI (codex-cli 0.144.1) in two rounds. Round 1 rejected the initial version, catching an abort on unpaired surrogates adjacent to escape characters (view-slice boundary validation) and a negative-indent behavior change — both real. The revised version avoids view slicing entirely; round 2: "Approved — no blocking findings. Both concerns are resolved, and well-formed output remains equivalent... Public interface is unchanged."
Signed-off-by: Codex CLI codex@openai.com
Validation
moon checkclean,moon fmtapplied, no.mbtichangesmoon test: 6716 passed, 0 failed (json 185/185, incl. new surrogate regression test)🤖 Generated with Claude Code