From 2edcecb7f9121dba1d7d680e6a038a0dc6acf7cc Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 19 Oct 2026 19:46:19 -0500 Subject: [PATCH] gh-159702: Add fast-path for float in _json encoder Optimize json encoder for floats: up to 1.3x faster. Avoid creating a temporary Unicode object when writing the JSON representation of a float: decode directly the ASCII-encoded string (PyOS_double_to_string()) into the writer. --- ...-10-19-19-55-47.gh-issue-159702.TL3O4R.rst | 2 + Modules/_json.c | 51 +++++++++++++++++-- 2 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-19-19-55-47.gh-issue-159702.TL3O4R.rst diff --git a/Misc/NEWS.d/next/Library/2026-10-19-19-55-47.gh-issue-159702.TL3O4R.rst b/Misc/NEWS.d/next/Library/2026-10-19-19-55-47.gh-issue-159702.TL3O4R.rst new file mode 100644 index 000000000000000..41a8fbd9266354f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-19-19-55-47.gh-issue-159702.TL3O4R.rst @@ -0,0 +1,2 @@ +:mod:`json`: Optimize the JSON encoder for floats to make it up to 1.3x +faster. Patch by Victor Stinner. diff --git a/Modules/_json.c b/Modules/_json.c index 3a724a3e72b185b..54299163e984e37 100644 --- a/Modules/_json.c +++ b/Modules/_json.c @@ -1532,6 +1532,52 @@ encoder_encode_float(PyEncoderObject *s, PyObject *obj) return PyFloat_Type.tp_repr(obj); } +static int +encoder_write_float(PyEncoderObject *s, PyUnicodeWriter *writer, PyObject *obj) +{ + /* Write the JSON representation of a PyFloat into writer. */ + double i = PyFloat_AS_DOUBLE(obj); + if (!isfinite(i)) { + if (!s->allow_nan) { + PyErr_Format( + PyExc_ValueError, + "Out of range float values are not JSON compliant: %R", + obj + ); + return -1; + } + + const char *str; + size_t len; + if (i > 0) { + str = "Infinity"; + len = strlen("Infinity"); + } + else if (i < 0) { + str = "-Infinity"; + len = strlen("-Infinity"); + } + else { + str = "NaN"; + len = strlen("NaN"); + } + return PyUnicodeWriter_WriteASCII(writer, str, len); + } + + // Similar to float_repr(), but writes directly into writer + char *buf = PyOS_double_to_string(PyFloat_AS_DOUBLE(obj), + 'r', 0, + Py_DTSF_ADD_DOT_0, + NULL); + if (!buf) { + PyErr_NoMemory(); + return -1; + } + int res = PyUnicodeWriter_WriteASCII(writer, buf, strlen(buf)); + PyMem_Free(buf); + return res; +} + static int encoder_write_string(PyEncoderObject *s, PyUnicodeWriter *writer, PyObject *obj) { @@ -1596,10 +1642,7 @@ encoder_listencode_obj(PyEncoderObject *s, PyUnicodeWriter *writer, return _steal_accumulate(writer, encoded); } else if (PyFloat_Check(obj)) { - PyObject *encoded = encoder_encode_float(s, obj); - if (encoded == NULL) - return -1; - return _steal_accumulate(writer, encoded); + return encoder_write_float(s, writer, obj); } else if (PyList_Check(obj) || PyTuple_Check(obj)) { if (_Py_EnterRecursiveCall(" while encoding a JSON object"))