Skip to content

Avoid copies in live update attachments#184

Draft
Alek99 wants to merge 1 commit into
mainfrom
agent/zero-copy-update-buffers
Draft

Avoid copies in live update attachments#184
Alek99 wants to merge 1 commit into
mainfrom
agent/zero-copy-update-buffers

Conversation

@Alek99

@Alek99 Alek99 commented Jul 22, 2026

Copy link
Copy Markdown
Member

Root cause

Live view-update assembly encoded arrays into the required contiguous f32/u8/u32 representation and then immediately copied every array again with ndarray.tobytes(). A 200k-point drill-in can attach roughly 4 MB across geometry, channels, and density values, so the redundant ownership copy sat directly on the zoom/selection latency path.

The anywidget comm and scatter/gather XYBF framing already accept contiguous buffer-protocol objects. Socket.IO is the one adapter that requires owned bytes and already has an explicit conversion boundary.

Changes

  • add an internal WireBuffer = bytes | memoryview contract and owner-retaining ndarray byte-view helper
  • retain contiguous encoded arrays through byte memoryview attachments in:
    • BufferWriter.add_f32 / add_u8
    • log-u8 density updates
    • gesture selection replies
    • programmatic row-selection replies
  • keep required dtype/non-contiguous conversion as the single encoding allocation
  • pass views directly through anywidget and encode_frame_parts
  • retain the explicit bytes() conversion at the python-socketio boundary
  • isolate callback-facing selection arrays only when on_select exists, so user mutation cannot rewrite an already assembled attachment; callback-free replies remain zero-copy
  • document attachment ownership/lifetime and transport-boundary rules

Frame bytes, metadata, selection masks, and protocol shape are unchanged.

Measured impact

A direct 1,000,000-element contiguous f32 attachment (4,000,000 bytes), measured after source allocation:

main this branch
attachment type bytes owner-retaining memoryview
peak traced allocation 4,000,393 B 896 B
byte parity exact exact

At 200k scale, isolated avoidable attachment assembly saved approximately:

  • density: 78,474 B
  • selection: 799,569 B
  • five-buffer drill: 3,998,453 B

End-to-end selection remains dominated by its earlier np.unique scan; the deterministic win here is removal of the second payload-sized ownership copy. Alternating same-process full-path timing showed forced-copy overhead of about 0.0015 ms (decimation), 0.0111 ms (density), 0.1236 ms (selection), and 0.0530 ms (drill).

Verification

  • expanded relevant core/adapter tests: 227 passed
  • independent framing/LOD/channel/scatter/view-state/widget/transport run: 217 passed; its one loopback sandbox denial passed separately with network access
  • real Socket.IO data-plane suite: 10 passed
  • broad suite: 2171 passed, 94 skipped
  • byte/frame parity covers owned bytes vs memoryviews and encode_frame_parts owner retention
  • lifetime tests cover GC ownership, shared mutation, C-contiguity, wrong dtype, and non-contiguous inputs
  • selection regressions cover callback mutation isolation and the callback-free borrowed-owner path
  • widget capture and real Socket.IO assert views internally and owned bytes at the required boundary
  • full Ruff lint and format checks: passed
  • changed production modules ty: passed
  • changed-file pre-commit and git diff --check: passed

The remaining broad-suite failure is the current-main minified-bundle marker mismatch, reproduced unchanged at b0f8780 and already fixed by #177. The sandbox-only loopback and Chromium checks both passed outside the restricted sandbox.

Closes #173

@codspeed-hq

codspeed-hq Bot commented Jul 22, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 13.26%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
✅ 96 untouched benchmarks
⏩ 1 skipped benchmark1

Performance Changes

Benchmark BASE HEAD Efficiency
test_density_view_exact_pan 12.6 ms 11.2 ms +13.26%

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing agent/zero-copy-update-buffers (6527b06) with main (2a7d898)

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown

Greptile Summary

This PR removes redundant copies from live-update binary attachments. The main changes are:

  • Adds an owner-retaining ndarray byte-view helper and shared wire-buffer type.
  • Uses memoryviews for LOD, density, and selection attachments.
  • Isolates selection callbacks from assembled attachment storage.
  • Keeps owned-byte conversion at the Socket.IO transport boundary.
  • Expands lifetime, framing, selection, widget, and transport tests.

Confidence Score: 5/5

This looks safe to merge.

No blocking issues found in the changed code.

T-Rex T-Rex Logs

What T-Rex did

  • Ran the core pytest suite for channel, lod, scatter, view_state, and widget tests; the command completed with 187 passed in 1.08s and exit code 0.
  • Compared pre- and post-PR data-plane test runs; both runs completed with all tests passing and exit code 0, with the post-PR run completing in 2.52s and the baseline in 2.54s.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
python/xy/_buffers.py Adds the wire-buffer type and C-contiguous owner-retaining byte-view helper.
python/xy/lod.py Changes BufferWriter to retain encoded arrays through memoryviews.
python/xy/channel.py Uses views for selection replies and copies callback-facing selection arrays.
python/xy/_figure.py Uses owner-retaining views for programmatic row-selection replies.
python/xy/interaction.py Propagates wire views through density and live-update assembly.
python/reflex-xy/reflex_xy/namespace.py Accepts wire views while preserving owned-byte conversion before Socket.IO emission.
python/reflex-xy/reflex_xy/registry.py Updates push callback and builder annotations for the expanded buffer contract.

Reviews (1): Last reviewed commit: "Avoid copies in live update attachments" | Re-trigger Greptile

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.

Update-path BufferWriter takes tobytes() copies where memoryviews suffice

1 participant