Skip to content

Support zero-copy memoryview uploads as request content#1043

Draft
technillogue wants to merge 1 commit into
pydantic:mainfrom
technillogue:memoryview-content-passthrough
Draft

Support zero-copy memoryview uploads as request content#1043
technillogue wants to merge 1 commit into
pydantic:mainfrom
technillogue:memoryview-content-passthrough

Conversation

@technillogue

@technillogue technillogue commented Jun 24, 2026

Copy link
Copy Markdown

Summary

Previously content=memoryview(...) fell through encode_content to the generic iterable branch, was iterated element-by-element into integers, and raised TypeError: object of type 'int' has no len().

httpx2:

  • encode_content now accepts a C-contiguous memoryview (e.g. over a bytearray, array.array, or an mmap) and yields it as a single chunk via a new BufferStream, normalised to a 1-D unsigned-byte view. A non-contiguous view raises rather than being silently copied.
  • BufferStream, unlike ByteStream, is treated as (replayable) streaming content - it is not eagerly read into a bytes object at request construction - so the buffer is never materialised up front.
  • memoryview is added to RequestContent/ResponseContent.

httpcore2:

  • _send_event uses h11's send_with_data_passthrough for Data events. With Content-Length framing the body is written straight to the network rather than via b"".join(...). Chunked framing and header writes are unchanged (coalesced into a single write).
  • Body memoryview chunks with itemsize > 1 are normalised to a flat byte view before h11, since both h11's Content-Length tracking (len()) and the backends' byte-count slicing assume one byte per element.

Net effect: a large buffer (e.g. a memoryview over an mmap) is only faulted into memory as it is written to the socket. A 128 MiB upload allocates ~18 MiB peak instead of a full-body copy.

Checklist

  • I understand that this PR may be closed in case there was no previous discussion. (This doesn't apply to typos!)
  • I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change.
  • I've updated the documentation accordingly.

Review in cubic

@codspeed-hq

codspeed-hq Bot commented Jun 24, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 15 untouched benchmarks
⏩ 7 skipped benchmarks1


Comparing technillogue:memoryview-content-passthrough (c78ecf3) with main (82b9e2d)

Open in CodSpeed

Footnotes

  1. 7 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

Previously `content=memoryview(...)` fell through `encode_content` to the
generic iterable branch, was iterated element-by-element into integers, and
raised `TypeError: object of type 'int' has no len()`.

httpx2:
* `encode_content` now accepts a C-contiguous `memoryview` (e.g. over a
  `bytearray`, `array.array`, or an `mmap`) and yields it as a single chunk via
  a new `BufferStream`, normalised to a 1-D unsigned-byte view. A
  non-contiguous view raises rather than being silently copied.
* `BufferStream`, unlike `ByteStream`, is treated as (replayable) streaming
  content - it is not eagerly read into a `bytes` object at request
  construction - so the buffer is never materialised up front.
* `memoryview` is added to `RequestContent`/`ResponseContent`.

httpcore2:
* `_send_event` uses h11's `send_with_data_passthrough` for `Data` events.
  With Content-Length framing the body is written straight to the network
  rather than via `b"".join(...)`. Chunked framing and header writes are
  unchanged (coalesced into a single write).
* Body `memoryview` chunks with itemsize > 1 are normalised to a flat byte
  view before h11, since both h11's Content-Length tracking (`len()`) and the
  backends' byte-count slicing assume one byte per element.

Net effect: a large buffer (e.g. a `memoryview` over an `mmap`) is only faulted
into memory as it is written to the socket. A 128 MiB upload allocates ~18 MiB
peak instead of a full-body copy.
@technillogue technillogue force-pushed the memoryview-content-passthrough branch from 70f8d89 to c78ecf3 Compare June 24, 2026 20:15
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.

1 participant