From e32789e915a4a6e156eac73d6512c01f3587619d Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Sat, 22 Aug 2026 20:16:37 +0200 Subject: [PATCH] gh-129711: Add a no-escape fast path to the _json str escapers 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. --- ...-08-13-09-00-00.gh-issue-129711.eScFP1.rst | 1 + Modules/_json.c | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-08-13-09-00-00.gh-issue-129711.eScFP1.rst diff --git a/Misc/NEWS.d/next/Library/2026-08-13-09-00-00.gh-issue-129711.eScFP1.rst b/Misc/NEWS.d/next/Library/2026-08-13-09-00-00.gh-issue-129711.eScFP1.rst new file mode 100644 index 00000000000000..dfe82854fa24ed --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-13-09-00-00.gh-issue-129711.eScFP1.rst @@ -0,0 +1 @@ +Speed up :func:`json.dump` for strings that need no escaping. diff --git a/Modules/_json.c b/Modules/_json.c index 3a724a3e72b185..aced8007328c82 100644 --- a/Modules/_json.c +++ b/Modules/_json.c @@ -223,6 +223,28 @@ ascii_escape_unicode_and_size(const void *input, int kind, Py_ssize_t input_char return rval; } +static PyObject * +quote_unescaped_unicode(PyObject *pystr) +{ + Py_ssize_t len = PyUnicode_GET_LENGTH(pystr); + PyObject *rval = PyUnicode_New(len + 2, PyUnicode_MAX_CHAR_VALUE(pystr)); + if (rval == NULL) { + return NULL; + } + int kind = PyUnicode_KIND(rval); + void *data = PyUnicode_DATA(rval); + PyUnicode_WRITE(kind, data, 0, '"'); + if (PyUnicode_CopyCharacters(rval, 1, pystr, 0, len) < 0) { + Py_DECREF(rval); + return NULL; + } + PyUnicode_WRITE(kind, data, len + 1, '"'); +#ifdef Py_DEBUG + assert(_PyUnicode_CheckConsistency(rval, 1)); +#endif + return rval; +} + static PyObject * ascii_escape_unicode(PyObject *pystr) { @@ -236,6 +258,10 @@ ascii_escape_unicode(PyObject *pystr) return NULL; } + if (output_size == input_chars + 2) { + return quote_unescaped_unicode(pystr); + } + return ascii_escape_unicode_and_size(input, kind, input_chars, output_size); } @@ -383,6 +409,10 @@ escape_unicode(PyObject *pystr) return NULL; } + if (output_size == input_chars + 2) { + return quote_unescaped_unicode(pystr); + } + return escape_unicode_and_size(input, kind, maxchar, input_chars, output_size); }