Skip to content

Commit 45fac46

Browse files
committed
Merge branch 'json-loads-opt' into HEAD
2 parents 7aec160 + d3cc3a7 commit 45fac46

3 files changed

Lines changed: 65 additions & 13 deletions

File tree

Lib/test/test_json/test_decode.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,28 @@ def test_limit_int(self):
180180
with self.assertRaises(ValueError):
181181
self.loads('1' * (maxdigits + 1))
182182

183+
def test_int_boundaries(self):
184+
# Values around the signed/unsigned 64-bit limits and the
185+
# 19-vs-20 digit fast-path threshold of the C accelerator.
186+
for s in ['0', '-0',
187+
'9223372036854775807', # LLONG_MAX
188+
'9223372036854775808', # LLONG_MAX + 1
189+
'-9223372036854775808', # LLONG_MIN
190+
'-9223372036854775809', # LLONG_MIN - 1
191+
'9999999999999999999', # largest 19-digit
192+
'-9999999999999999999',
193+
'18446744073709551615', # ULLONG_MAX (20 digits)
194+
'18446744073709551616', # ULLONG_MAX + 1
195+
'10000000000000000000', # smallest 20-digit
196+
'-10000000000000000000']:
197+
with self.subTest(s=s):
198+
self.assertEqual(self.loads(s), int(s))
199+
200+
def test_long_float(self):
201+
# A float longer than the C accelerator's stack buffer.
202+
s = '0.' + '1' * 200
203+
self.assertEqual(self.loads(s), float(s))
204+
183205
def test_explicit_cls_skips_json_decoder_default(self):
184206
class CustomDecoder:
185207
pass
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Speed up :func:`json.loads` and :func:`json.load` parsing of numbers.

Modules/_json.c

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -997,7 +997,6 @@ _match_number_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_
997997
Py_ssize_t idx = start;
998998
int is_float = 0;
999999
PyObject *rval;
1000-
PyObject *numstr = NULL;
10011000
PyObject *custom_func;
10021001

10031002
str = PyUnicode_DATA(pystr);
@@ -1064,32 +1063,62 @@ _match_number_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_
10641063

10651064
if (custom_func) {
10661065
/* copy the section we determined to be a number */
1067-
numstr = PyUnicode_FromKindAndData(kind,
1068-
(char*)str + kind * start,
1069-
idx - start);
1066+
PyObject *numstr = PyUnicode_FromKindAndData(kind,
1067+
(char*)str + kind * start,
1068+
idx - start);
10701069
if (numstr == NULL)
10711070
return NULL;
10721071
rval = PyObject_CallOneArg(custom_func, numstr);
1072+
Py_DECREF(numstr);
10731073
}
10741074
else {
10751075
Py_ssize_t i, n;
1076-
char *buf;
1076+
1077+
/* Fast path for integers with at most 19 digits (excluding the
1078+
optional minus sign): the magnitude always fits in an unsigned
1079+
long long, so we construct the result from it directly. */
1080+
int neg = (PyUnicode_READ(kind, str, start) == '-');
1081+
if (!is_float && idx - start - neg <= 19) {
1082+
unsigned long long value = 0;
1083+
for (i = start + neg; i < idx; i++) {
1084+
value = value * 10 + (PyUnicode_READ(kind, str, i) - '0');
1085+
}
1086+
*next_idx_ptr = idx;
1087+
rval = PyLong_FromUnsignedLongLong(value);
1088+
if (neg && rval != NULL) {
1089+
Py_SETREF(rval, PyNumber_Negative(rval));
1090+
}
1091+
return rval;
1092+
}
1093+
10771094
/* Straight conversion to ASCII, to avoid costly conversion of
10781095
decimal unicode digits (which cannot appear here) */
10791096
n = idx - start;
1080-
numstr = PyBytes_FromStringAndSize(NULL, n);
1081-
if (numstr == NULL)
1082-
return NULL;
1083-
buf = PyBytes_AS_STRING(numstr);
1097+
char stackbuf[64];
1098+
PyObject *numstr = NULL;
1099+
char *buf;
1100+
if (n < (Py_ssize_t)sizeof(stackbuf)) {
1101+
buf = stackbuf;
1102+
buf[n] = '\0';
1103+
}
1104+
else {
1105+
numstr = PyBytes_FromStringAndSize(NULL, n);
1106+
if (numstr == NULL)
1107+
return NULL;
1108+
buf = PyBytes_AS_STRING(numstr);
1109+
}
10841110
for (i = 0; i < n; i++) {
10851111
buf[i] = (char) PyUnicode_READ(kind, str, i + start);
10861112
}
1087-
if (is_float)
1088-
rval = PyFloat_FromString(numstr);
1089-
else
1113+
if (is_float) {
1114+
double d = PyOS_string_to_double(buf, NULL, NULL);
1115+
rval = (d == -1.0 && PyErr_Occurred()) ? NULL : PyFloat_FromDouble(d);
1116+
}
1117+
else {
10901118
rval = PyLong_FromString(buf, NULL, 10);
1119+
}
1120+
Py_XDECREF(numstr);
10911121
}
1092-
Py_DECREF(numstr);
10931122
*next_idx_ptr = idx;
10941123
return rval;
10951124
}

0 commit comments

Comments
 (0)