GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — [WIP] gh-129813, PEP 782: Add PyBytesWriter C API by vstinner · Pull Request #131681 · python/cpython

Conversation

@vstinner

@vstinner vstinner commented Mar 24, 2025 •
edited by bedevere-app Bot
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

Add functions:

  • PyBytesWriter_Create()
  • PyBytesWriter_Discard()
  • PyBytesWriter_Finish()
  • PyBytesWriter_FinishWithSize()
  • PyBytesWriter_FinishWithEndPointer()
  • PyBytesWriter_Data()
  • PyBytesWriter_Allocated()
  • PyBytesWriter_SetSize()
  • PyBytesWriter_Resize()
  • Issue: [C API] PEP 782: Add PyBytesWriter API #129813

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@vstinner vstinner added the skip news label Mar 24, 2025
@bedevere-app bedevere-app Bot mentioned this pull request Mar 24, 2025
[C API] PEP 782: Add PyBytesWriter API #129813
Closed
@vstinner
vstinner force-pushed the bytes_writer_size branch from 459f3d8 to 9097e5f Compare March 24, 2025 16:55
@vstinner
pythongh-129813: Add PyBytesWriter C API (flavor with size)
e24d40e
Add functions:

* PyBytesWriter_Create()
* PyBytesWriter_Discard()
* PyBytesWriter_Finish()
* PyBytesWriter_FinishWithSize()
* PyBytesWriter_FinishWithEndPointer()
* PyBytesWriter_Data()
* PyBytesWriter_Allocated()
* PyBytesWriter_SetSize()
* PyBytesWriter_Resize()
@vstinner
vstinner force-pushed the bytes_writer_size branch from 9097e5f to e24d40e Compare March 24, 2025 16:56
@vstinner vstinner changed the title [WIP] gh-129813: Add PyBytesWriter C API (with size flavor) [WIP] gh-129813: Add PyBytesWriter C API (flavor with size) Mar 24, 2025
vstinner added 24 commits March 25, 2025 13:07
@vstinner
Remove PyBytesWriter_SetSize()
8761a9b
@vstinner
Add tests
92e1294
* Add PyBytesWriter_GetSize()
* Rename:

  * PyBytesWriter_Data() => PyBytesWriter_GetData()
  * PyBytesWriter_Allocated() => PyBytesWriter_GetAllocated()
@vstinner
Add PyBytesWriter_WriteBytes()
eff71b5
@vstinner
Add PyBytesWriter_Format()
31c7ca7
@vstinner
Fix build on Windows
86d0fd9
@vstinner
Add PyBytesWriter_ResizeAndUpdatePointer() function
79fa5f8
@vstinner
Convert _PyBytes_FromIterator()
bf60f7f
@vstinner
Add _PyBytesWriter_CreateByteArray()
62a15be
Convert _PyBytes_FromHex().
@vstinner
Convert _PyBytes_FormatEx()
0a70d70
@vstinner
Rename PyBytesWriter_FinishWithPointer()
457e21a
@vstinner
Add PyBytesWriter_GrowAndUpdatePointer()
40ef4e1
@vstinner
Make PyBytesWriter_ResizeAndUpdatePointer() private
0313087
@vstinner
Make PyBytesWriter_GetAllocated() private
c8ac889
@vstinner
Don't overallocate for bytearray()
7095ac4
@vstinner
Move _PyBytesWriter_CreateByteArray() to the internal C API
befd574
@vstinner
Move code
3ba1d1c
@vstinner
Add examples
ede2776
@vstinner
Add high-level API example
be56685
@vstinner
Fix tests
1135390
@vstinner
fix linter
000ba58
@vstinner
Convert more functions
b864c26
Replace  PyBytes_FromStringAndSize(NULL, 0) with
Py_GetConstant(Py_CONSTANT_EMPTY_BYTES).
@vstinner
Convert _hashopenssl function
6d7e37d
@vstinner
Detect strlen() overflow
d8a4659
@vstinner
Fix mmap
ed00f95
vstinner added 2 commits March 31, 2025 18:49
@vstinner
Grow() can now shrink the buffer
6307895
@vstinner
Fix WriteBytes()
18d41ff
@vstinner vstinner changed the title [WIP] gh-129813: Add PyBytesWriter C API (flavor with size) [WIP] gh-129813, PEP 782: Add PyBytesWriter C API Apr 2, 2025
@vstinner
Merge branch 'main' into bytes_writer_size
4cf51f3
@vstinner

vstinner commented Apr 22, 2025 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member Author

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().

Microbenchmark on 3 functions, to compare the private _PyBytesWriter (ref) to the new public PyBytesWriter (change):

  • bytes(list)
  • bytes.fromhex(str)
  • binascii.b2a_uu(bytes)
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 change
from list 100 631 ns 623 ns: 1.01x faster
from hex 100 141 ns 145 ns: 1.03x slower
from hex 1,000 1.03 us 1.04 us: 1.00x slower
b2a_uu 112 ns 111 ns: 1.01x faster
Geometric mean (ref) 1.00x slower

Benchmark hidden because not significant (1): from list 1,000

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner

vstinner commented Apr 22, 2025

Copy link
Copy Markdown
Member Author

Benchmark comparing PyBytes_FromStringAndSize(NULL, length) (ref) to PyBytesWriter_Create() (change).

Benchmark:

import pyperf

SIZES = (10, 100, 500)

runner = pyperf.Runner()
for size in SIZES:
    large_int = (2 ** (size * 8) - 1)
    runner.bench_func(f'to_bytes({size})', large_int.to_bytes, size)
for size in SIZES:
    mem = memoryview(b'x' * size)
    runner.bench_func(f'memoryview({size}).tobytes()', mem.tobytes)

Result:

Benchmark ref change
to_bytes(10) 56.3 ns 66.4 ns: 1.18x slower (+10.1 ns)
to_bytes(100) 152 ns 162 ns: 1.06x slower (+10 ns)
to_bytes(500) 563 ns 559 ns: 1.01x faster (+4 ns)
memoryview(10).tobytes() 37.5 ns 47.0 ns: 1.25x slower (+9.5 ns)
memoryview(100).tobytes() 35.3 ns 46.6 ns: 1.32x slower (+11.3 ns)
memoryview(500).tobytes() 45.5 ns 55.3 ns: 1.21x slower (+9.8 ns)
Geometric mean (ref) 1.16x slower

It's hard to beat PyBytes_FromStringAndSize(NULL, length) performance, since PyBytesWriter_Create() is a wrapper built on top of PyBytes_FromStringAndSize(NULL, length).

There is an overhead around 10 ns when using PyBytesWriter.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner vstinner mentioned this pull request Apr 22, 2025
PEP 782: Add PyBytesWriter C API capi-workgroup/decisions#62
Closed
4 of 5 tasks
@serhiy-storchaka

serhiy-storchaka commented May 6, 2025

Copy link
Copy Markdown
Member

Could you please benchmark the following?

  • ASCII, Latin1 and UTF-8 encoders. For ASCII-only and non-ASCII data.
  • The backslashreplace and xmlcharrefreplace error handlers (encoding).
  • PyBytes_FromFormat(). Especially with few % formats and large raw data between them.
  • PyBytes_DecodeEscape().
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner

vstinner commented May 6, 2025

Copy link
Copy Markdown
Member Author

I wrote a big PR to show how PEP 782 would look like and how it's being used. But if PEP 782 is accepted, I will only start by adding the API without using it. Then I will write separated changes to use the new API and run benchmarks on each change.

ASCII, Latin1 and UTF-8 encoders. For ASCII-only and non-ASCII data.

I didn't modify these encoders, they still use the private _PyBytesWriter API.

The backslashreplace and xmlcharrefreplace error handlers (encoding).

Same.

If I modify these encoders and error handlers later, I will run benchmarks to decide if it's acceptable to use the public API or not.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner

vstinner commented May 6, 2025

Copy link
Copy Markdown
Member Author

Microbenchmark on PyBytes_FromFormat() and PyBytes_DecodeEscape() functions.

Details
import pyperf
runner = pyperf.Runner()

import ctypes
from ctypes import pythonapi, py_object
from ctypes import (
    c_int, c_uint,
    c_long, c_ulong,
    c_size_t, c_ssize_t,
    c_char_p)

PyBytes_FromFormat = pythonapi.PyBytes_FromFormat
PyBytes_FromFormat.argtypes = (c_char_p,)
PyBytes_FromFormat.restype = py_object

PyBytes_DecodeEscape = pythonapi.PyBytes_DecodeEscape
PyBytes_DecodeEscape.argtypes = (c_char_p, c_size_t, c_char_p, c_size_t, c_char_p)
PyBytes_DecodeEscape.restype = py_object

runner.bench_func('Format hello world', PyBytes_FromFormat, b'Hello %s !', b'world')
fmt = (b'Hell%c' + b' ' * 1024 + b' %s')
runner.bench_func('Format long format', PyBytes_FromFormat, fmt, c_int(ord('o')), b'world')

s = b'abc\\ndef\\x40.'
runner.bench_func('Decode simple', PyBytes_DecodeEscape, s, len(s), None, 0, b'unused')
s = b'x' * 1024
runner.bench_func('Decode long copy', PyBytes_DecodeEscape, s, len(s), None, 0, b'unused')
s = b'\\x40' * 1024
runner.bench_func('Decode long \\x40', PyBytes_DecodeEscape, s, len(s), None, 0, b'unused')

Results:

Benchmark ref pep782
Format long format 1.06 us 1.04 us: 1.02x faster
Decode simple 776 ns 743 ns: 1.04x faster
Decode long copy 1.38 us 1.34 us: 1.03x faster
Decode long \x40 2.70 us 2.67 us: 1.01x faster
Geometric mean (ref) 1.02x faster

Benchmark hidden because not significant (1): Format hello world

I'm not sure why PEP 782 is faster, but at least it's not slower :-)

I build Python with gcc -O3 (without PGO, LTO, CPU isolation).

serhiy-storchaka reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

vstinner added 2 commits August 13, 2025 15:02
@vstinner
Merge branch 'main' into bytes_writer_size
93f8447
@vstinner
Merge branch 'main' into bytes_writer_size
a261a43
@vstinner vstinner mentioned this pull request Sep 12, 2025
gh-129813, PEP 782: Add PyBytesWriter_Format() #138824
Merged
@vstinner vstinner closed this Sep 12, 2025
@vstinner

vstinner commented Sep 12, 2025

Copy link
Copy Markdown
Member Author

I started to split this huge PR into smaller PRs, see PRs attached to the issue #129813.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner
vstinner deleted the bytes_writer_size branch December 3, 2025 15:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@StanFromIreland StanFromIreland Awaiting requested review from StanFromIreland StanFromIreland will be requested when the pull request is marked ready for review StanFromIreland is a code owner
@corona10 corona10 Awaiting requested review from corona10 corona10 will be requested when the pull request is marked ready for review corona10 is a code owner
@erlend-aasland erlend-aasland Awaiting requested review from erlend-aasland erlend-aasland will be requested when the pull request is marked ready for review erlend-aasland is a code owner
@serhiy-storchaka serhiy-storchaka Awaiting requested review from serhiy-storchaka serhiy-storchaka will be requested when the pull request is marked ready for review serhiy-storchaka is a code owner
@gpshead gpshead Awaiting requested review from gpshead gpshead will be requested when the pull request is marked ready for review gpshead is a code owner
@picnixz picnixz Awaiting requested review from picnixz picnixz will be requested when the pull request is marked ready for review picnixz is a code owner
@ericsnowcurrently ericsnowcurrently Awaiting requested review from ericsnowcurrently ericsnowcurrently will be requested when the pull request is marked ready for review ericsnowcurrently is a code owner
@ZeroIntensity ZeroIntensity Awaiting requested review from ZeroIntensity ZeroIntensity will be requested when the pull request is marked ready for review ZeroIntensity is a code owner

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

skip news

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

2 participants

@vstinner @serhiy-storchaka
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.