Skip to content

json: Fast path for string encoding #133239

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

nineteendo
Copy link
Contributor

@nineteendo nineteendo commented May 1, 2025

pyperformance (with --enable-optimizations)

main.json
=========

Performance version: 1.11.0
Python version: 3.14.0a7+ (64-bit) revision feac343d1a
Report on macOS-13.7.5-x86_64-i386-64bit-Mach-O
Number of logical CPUs: 8
Start date: 2025-05-01 09:59:42.291600
End date: 2025-05-01 10:00:07.954799

speedup-json-encode.json
========================

Performance version: 1.11.0
Python version: 3.14.0a7+ (64-bit) revision 930e93813c
Report on macOS-13.7.5-x86_64-i386-64bit-Mach-O
Number of logical CPUs: 8
Start date: 2025-05-01 10:00:27.583813
End date: 2025-05-01 10:00:48.572728

### json_dumps ###
Mean +- std dev: 16.0 ms +- 2.5 ms -> 12.8 ms +- 0.4 ms: 1.25x faster
Significant (t=13.80)

jsonyx-performance-tests (with --enable-optimizations)

wannes@Stefans-iMac jsonyx-performance-tests % ../cpython/main/python.exe bench               
| encode                                      |  json | json (setuptools) | reference time |
|:--------------------------------------------|------:|------------------:|---------------:|
| List of 256 ASCII strings                   | 1.02x |             1.00x |       56.51 μs |
| List of 256 dicts with 1 int                | 1.03x |             1.00x |       88.67 μs |
| Medium complex object                       | 1.23x |             1.00x |      125.09 μs |
| List of 256 strings                         | 1.11x |             1.00x |      273.81 μs |
| Complex object                              | 1.09x |             1.00x |     1434.13 μs |
| Dict with 256 lists of 256 dicts with 1 int | 1.03x |             1.00x |    22795.94 μs |
wannes@Stefans-iMac jsonyx-performance-tests % ../cpython/speedup-json-encode/python.exe bench
| encode                                      |  json | json (setuptools) | reference time |
|:--------------------------------------------|------:|------------------:|---------------:|
| List of 256 ASCII strings                   | 0.56x |             1.00x |       52.03 μs |
| List of 256 dicts with 1 int                | 0.84x |             1.00x |       87.97 μs |
| Medium complex object                       | 0.97x |             1.00x |      126.79 μs |
| List of 256 strings                         | 1.31x |             1.00x |      280.40 μs |
| Complex object                              | 1.11x |             1.00x |     1430.89 μs |
| Dict with 256 lists of 256 dicts with 1 int | 0.84x |             1.00x |    23435.69 μs |

@methane
Copy link
Member

methane commented May 1, 2025

https://gist.github.com/methane/e080ec9783db2a313f40a2b9e1837e72

Benchmark main #133186 #133239
json_dumps: List of 256 booleans 16.6 us not significant 17.2 us: 1.03x slower
json_dumps: List of 256 ASCII strings 67.9 us 34.7 us: 1.96x faster 46.5 us: 1.46x faster
json_dumps: List of 256 dicts with 1 int 122 us 101 us: 1.21x faster 112 us: 1.09x faster
json_dumps: Medium complex object 205 us 173 us: 1.18x faster 189 us: 1.09x faster
json_dumps: List of 256 strings 330 us 302 us: 1.09x faster 298 us: 1.11x faster
json_dumps: Complex object 2.57 ms 1.96 ms: 1.31x faster not significant
json_dumps: Dict with 256 lists of 256 dicts with 1 int 30.5 ms 26.5 ms: 1.15x faster 29.4 ms: 1.04x faster
json_dumps(ensure_ascii=False): List of 256 booleans 16.6 us not significant 17.2 us: 1.03x slower
json_dumps(ensure_ascii=False): List of 256 ASCII strings 68.1 us 34.6 us: 1.96x faster 46.5 us: 1.46x faster
json_dumps(ensure_ascii=False): List of 256 dicts with 1 int 122 us 101 us: 1.21x faster 112 us: 1.09x faster
json_dumps(ensure_ascii=False): Medium complex object 205 us 172 us: 1.19x faster 188 us: 1.09x faster
json_dumps(ensure_ascii=False): List of 256 strings 329 us 303 us: 1.09x faster 298 us: 1.11x faster
json_dumps(ensure_ascii=False): Complex object 2.56 ms 1.95 ms: 1.31x faster not significant
json_dumps(ensure_ascii=False): Dict with 256 lists of 256 dicts with 1 int 30.6 ms 26.5 ms: 1.15x faster 29.4 ms: 1.04x faster
json_loads: List of 256 floats 91.4 us 88.3 us: 1.03x faster not significant
json_loads: List of 256 strings 848 us 816 us: 1.04x faster not significant
Geometric mean (ref) 1.13x faster 1.05x faster

Benchmark hidden because not significant (10): json_dumps: List of 256 floats, json_dumps(ensure_ascii=False): List of 256 floats, json_loads: List of 256 booleans, json_loads: List of 256 ASCII strings, json_loads: List of 256 dicts with 1 int, json_loads: Medium complex object, json_loads: Complex object, json_loads: Dict with 256 lists of 256 dicts with 1 int, json_loads: List of 256 stringsensure_ascii=False, json_loads: Complex objectensure_ascii=False

Comment on lines +230 to +250
for (i = 0, output_size = 0; i < input_chars; i++) {
Py_UCS4 c = PyUnicode_READ(kind, input, i);
Py_ssize_t d;
if (S_CHAR(c)) {
d = 1;
}
else {
switch(c) {
case '\\': case '"': case '\b': case '\f':
case '\n': case '\r': case '\t':
d = 2; break;
default:
d = c >= 0x10000 ? 12 : 6;
}
}
if (output_size > PY_SSIZE_T_MAX - d) {
PyErr_SetString(PyExc_OverflowError, "string is too long to escape");
return NULL;
}
output_size += d;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we rewrite this to be faster? Because that's the only difference in the fast path between both PRs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants