Python optimizations - #280
Conversation
There was a problem hiding this comment.
All reported issues were addressed across 4 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
…uilds Profiling jiter-python showed ~10% of object-heavy parses spent in pthread mutex lock/unlock, because the string cache mutex was acquired once per string. The parser now takes the lock once in `parse()` and passes the guard through `StringMaybeCache::get_key`/`get_value`. The lock is acquired with `try_lock` so that on free-threaded builds a concurrent parse falls back to creating uncached strings rather than serialising on the mutex. `maturin develop --release` and the CI wheel builds use `profile.release`, which had no `lto`/`codegen-units` settings (only `profile.bench` did); add them so wheels get the same optimisation as the Rust benchmarks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011axe7edsWuf1JqHQS88Npo
`pystring_ascii_new` called `PyUnicode_New` for every ASCII string, including one-character ones, which the string cache deliberately skips. CPython keeps interned singletons for all 1-byte ASCII strings and `PyUnicode_FromStringAndSize` returns them without allocating, which is what `json`, ujson and orjson all end up doing. On the `array_short_arrays` benchmark (40,000 one-character strings per parse) this removes 40,000 allocations and deallocations: 940µs -> 660µs, taking jiter from slowest to fastest of the parsers benchmarked. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011axe7edsWuf1JqHQS88Npo
Ranks packages by the geometric mean of their per-case slowdown relative to the fastest package, and shows the worst case and number of cases won. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011axe7edsWuf1JqHQS88Npo
Each parser's output is compared against the stdlib result before timing; mismatches are shown as "invalid" instead of a slowdown and excluded from the summary. orjson returns lossy floats for the massive_ints_array case, so its 4x lead there was never a like-for-like comparison. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011axe7edsWuf1JqHQS88Npo
c5ee140 to
daf56bf
Compare
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Merging this PR will regress 3 benchmarks
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
Holding the cache lock for the whole parse meant a Python callback that runs while the parser allocates (GC callbacks and `__del__` on CPython <= 3.11, the `decimal` import in decimal mode) would deadlock if it called `jiter.cache_clear()` or `cache_usage()`. A thread-local flag now records that the lock is held by the current thread, and those functions panic with a clear message instead of blocking on their own lock; other threads still block as before. Also make the benchmark script tolerate a parser raising on a case (shown as "error", not timed) and a parser with no valid cases. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011axe7edsWuf1JqHQS88Npo
There was a problem hiding this comment.
All reported issues were addressed across 5 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Summary by cubic
Speeds up Python parsing by locking the string cache once per parse instead of per string, and enables the same release optimizations for wheel builds that the Rust benchmarks already had.
Also makes the Python benchmark script more correct: it now validates each parser's output against
json.loadsand shows a cross-case summary.try_lock, so concurrent parses fall back to uncached strings instead of blocking on the mutex.profile.releasenow useslto = "fat"andcodegen-units = 1for compiled wheels.Written for commit fc620f4. Summary will update on new commits.