Skip to content

gh-156241: Add a no-escape fast path to the _json str escapers - #156242

Open
eendebakpt wants to merge 1 commit into
python:mainfrom
eendebakpt:json_escape_fastpath
Open

gh-156241: Add a no-escape fast path to the _json str escapers#156242
eendebakpt wants to merge 1 commit into
python:mainfrom
eendebakpt:json_escape_fastpath

Conversation

@eendebakpt

@eendebakpt eendebakpt commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

ascii_escape_unicode() and escape_unicode() now return the input bulk-copied with surrounding quotes when nothing needs escaping, via a shared quote_unescaped_unicode() helper, like the writer-based write_escaped_ascii()/write_escaped_unicode() already do.

Benchmark main PR
dumps ascii 10 342 ns 349 ns: ~same
dumps ascii 100 508 ns 442 ns: 1.15x faster
dumps ascii 1 kB 1.73 µs 1.19 µs: 1.45x faster
dumps ascii 16 kB 21.4 µs 13.2 µs: 1.63x faster
dumps unicode 1 kB, ensure_ascii=False 3.04 µs 1.80 µs: 1.69x faster
dump list of 100 strings 23.2 µs 21.1 µs: 1.10x faster
dumps escapes 100 531 ns 520 ns: ~same
dumps list of 3 short strings 1.30 µs 1.29 µs: ~same
Benchmark script
"""Benchmark for the _json str-escaper no-escape fast path.

json.dumps(str) uses encode_basestring[_ascii] directly; json.dump() uses
it for every encoded string; dumps() of a container uses the writer-based
escapers (unchanged).
"""
import functools
import io
import json

import pyperf

CASES = {
    "ascii 10": "callsign-7",
    "ascii 100": "The quick brown fox jumps over the lazy dog. " * 2 + "0123456789",
    "ascii 1k": "abcdefghij" * 100,
    "ascii 16k": "abcdefghij" * 1600,
    "escapes 100": ('line\n"quoted"\ttab\\ ' * 5)[:100],
    "unicode 1k": "héllo wörld unicode … " * 47,
}


def dumps_obj(o, **kw):
    json.dumps(o, **kw)


if __name__ == "__main__":
    runner = pyperf.Runner()
    for name, s in CASES.items():
        runner.bench_func("dumps %s" % name, dumps_obj, s)
    runner.bench_func("dumps unicode 1k noascii",
                      functools.partial(dumps_obj, CASES["unicode 1k"],
                                        ensure_ascii=False))
    runner.bench_func("dumps list of 3 short strings", dumps_obj,
                      ["alpha-01", "beta-002", "gamma-03"])

    def dump_obj(o):
        json.dump(o, io.StringIO())
    runner.bench_func("dump list of 3 short strings", dump_obj,
                      ["alpha-01", "beta-002", "gamma-03"])
    runner.bench_func("dump list of 100 strings", dump_obj,
                      ["string number %04d of the benchmark payload" % i
                       for i in range(100)])

Run with python bench_escape.py --fast -o out.json per build; compare with pyperf compare_to base.json branch.json --table.

ascii_escape_unicode() and escape_unicode() now return the input
bulk-copied with surrounding quotes when nothing needs escaping, via a
shared quote_unescaped_unicode() helper, like the writer-based
write_escaped_ascii()/write_escaped_unicode() already do in-place.
This speeds up json.dumps() of clean strings by 1.2x-2.2x.  No
behavior change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant