Skip to content

[WIP] gh-129813: Add PyBytesWriter C API #129814

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

Closed
wants to merge 8 commits into from

Conversation

vstinner
Copy link
Member

@vstinner vstinner commented Feb 7, 2025

  • Replace usage of the old private _PyBytesWriter with the new public PyBytesWriter C API.
  • Remove the old private _PyBytesWriter C API.
  • Add a freelist for PyBytesWriter_Create().
  • TODO: write doc
  • TODO: document new functions in What's New and Changelog

* Replace usage of the old private _PyBytesWriter with the new public
  PyBytesWriter C API.
* Remove the old private _PyBytesWriter C API.
* Add a freelist for PyBytesWriter_Create().
* TODO: write doc
* TODO: document new functions in What's New and Changelog
@vstinner
Copy link
Member Author

vstinner commented Feb 7, 2025

The PR is big because it also replaces usage of the old private API with new public API. If the API is approved, I will split the PR into smaller pieces and measure the performance impact of these changes.

@vstinner
Copy link
Member Author

vstinner commented Feb 7, 2025

Some functions should be optimized after the removal of the private min_size member:

  • unicode_encode_ucs1()
  • utf8_encoder()
  • PyBytes_FromFormatV()

These functions allocate a little bit too much memory, extend argument of PyBytesWriter_Extend() should be adjusted.

I just tried to make the code work, not to optimize it.

@vstinner
Copy link
Member Author

vstinner commented Feb 7, 2025

This change has no impact on performance, even if the new public API allocates memory on the heap, instead of allocating on the stack. It uses a freelist to optimize PyBytesWriter_Create().

Example of microbenchmark on 3 functions:

import pyperf
import binascii

runner = pyperf.Runner()
runner.bench_func('from list 100', bytes, list(b'x' * 100))
runner.bench_func('from list 1,000', bytes, list(b'x' * 1_000))

runner.bench_func('from hex 100', bytes.fromhex, bytes(range(100)).hex())
runner.bench_func('from hex 1,000', bytes.fromhex, (b'x' * 1_000).hex())

runner.bench_func('b2a_uu', binascii.b2a_uu, b'x' * 45)

Result:

Benchmark ref public
from list 100 672 ns 647 ns: 1.04x faster
from list 1,000 6.22 us 6.12 us: 1.02x faster
from hex 100 143 ns 145 ns: 1.02x slower
from hex 1,000 1.02 us 1.03 us: 1.00x slower
Geometric mean (ref) 1.01x faster

Benchmark hidden because not significant (1): b2a_uu

@cmaloney
Copy link
Contributor

pseudo-tangential idea: Could this instead just be a C wrapper for io.BytesIO? Working to try to get to one fast implementation to read/write a bytes object.

@vstinner
Copy link
Member Author

pseudo-tangential idea: Could this instead just be a C wrapper for io.BytesIO?

io.BytesIO API is basically the write(bytes) method, whereas proposed PyBytesWriter API gives directly a pointer into a buffer. It's a different API. I don't think that io.BytesIO API can or should be modified to give directly a pointer. io.BytesIO is more complex since it allows changing the position, the seek() method, and also reading, the read() method.

@vstinner
Copy link
Member Author

I created a discussion: https://discuss.python.org/t/add-pybyteswriter-public-c-api/81182

It seems like most developers are confused by the API which requires to pass writer and buf to most functions. I abandon this API.

@vstinner vstinner closed this Mar 12, 2025
@vstinner vstinner deleted the bytes_writer branch March 12, 2025 11:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants