THRIFT-6069: python: avoid utf8 copy during fastbinary string encode#3595
THRIFT-6069: python: avoid utf8 copy during fastbinary string encode#3595markjm wants to merge 2 commits into
Conversation
Use PyUnicode_AsUTF8AndSize when available, with a fallback for older Python 3 releases, so UTF-8 strings can be encoded without allocating an intermediate PyBytes object. Performance (50k iterations, warmed) | Workload | Baseline | This commit | Speedup | |----------|----------|-------------|---------| | encode simple (30B, 1 string) | 0.60 us | 0.55 us | 1.09x | | encode 10-string (182B) | 1.44 us | 1.01 us | 1.43x | | encode complex (395B) | 3.02 us | 2.56 us | 1.18x | The more string fields a struct has, the larger the gain. Decode is unchanged.
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR strengthens Thrift’s Python fastbinary path by improving UTF-8 string encoding in the C extension and adding a regression test that exercises accelerated encoding/decoding for TApplicationException.
Changes:
- Add a unit test covering accelerated UTF-8 roundtrip for
TApplicationExceptionwhenfastbinaryis available. - Update the C extension to encode Python
strvalues viaPyUnicode_AsUTF8AndSize(avoids intermediate bytes object creation) while preserving 32-bit length checks.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| lib/py/test/thrift_TBinaryProtocol.py | Adds accelerated-protocol test coverage and related fixtures for TApplicationException. |
| lib/py/src/ext/protocol.tcc | Optimizes T_STRING encoding for Python str by writing UTF-8 bytes directly with length validation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| from thrift.Thrift import TApplicationException | ||
| from thrift.protocol.TBinaryProtocol import TBinaryProtocol | ||
| from thrift.protocol.TBinaryProtocol import TBinaryProtocolAcceleratedFactory |
| APPLICATION_EXCEPTION_TYPEARGS = [ | ||
| TApplicationException, | ||
| ( | ||
| None, | ||
| (1, 11, "message", "UTF8", None), | ||
| (2, 8, "type", None, None), | ||
| ), | ||
| ] |
|
|
||
| otrans = TTransport.TMemoryBuffer() | ||
| oproto = TBinaryProtocolAcceleratedFactory(fallback=False).getProtocol(otrans) | ||
| oproto.trans.write(oproto._fast_encode(original, APPLICATION_EXCEPTION_TYPEARGS)) |
|
|
||
| itrans = TTransport.TMemoryBuffer(otrans.getvalue()) | ||
| iproto = TBinaryProtocolAcceleratedFactory(fallback=False).getProtocol(itrans) | ||
| decoded = iproto._fast_decode(None, iproto, APPLICATION_EXCEPTION_TYPEARGS) |
| impl()->writeI32(static_cast<int32_t>(len)); | ||
| return writeBuffer(const_cast<char*>(str), static_cast<size_t>(len)); |
Change the internal fastbinary writeBuffer helper to accept const input buffers so the zero-copy UTF-8 encode path does not need to const_cast CPython-managed string storage.
Code reviewFound 2 issues:
thrift/lib/py/src/ext/protocol.tcc Lines 469 to 473 in 0691570
Lines 57 to 71 in 35c1a53 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
Hi - I figured I'd share a few perf optimizations we are using internally. We are still on an older thrift version (😢 ), so I did use some agent magic to port these to the head of this repo. My testing was primarily on my branch - in this case, for example, we only care about p3.12+, so i added some compat for when they added PyUnicode_AsUTF8AndSize in py3.3 (impressed the repo still suports py2!)
This one is 1 of 3 PRs
Use PyUnicode_AsUTF8AndSize when available, with a fallback for older Python 3 releases, so UTF-8 strings can be encoded without allocating an intermediate PyBytes object.
Performance (50k iterations, warmed)
The more string fields a struct has, the larger the gain. Decode is unchanged.