Skip to content

Perf/dma tail wide memset - #896

Draft
diegokingston wants to merge 2 commits into
feat/dma-memcpyfrom
perf/dma-tail-wide-memset
Draft

Perf/dma tail wide memset#896
diegokingston wants to merge 2 commits into
feat/dma-memcpyfrom
perf/dma-tail-wide-memset

Conversation

@diegokingston

Copy link
Copy Markdown
Collaborator

No description provided.

Routes the guest's strong `memset` symbol through a bounded DMA ecall, the
same shape as the memcpy stub #874 added, and proves each chunk with a new
20-column DMA_SET table.

memset is cheaper than memcpy rather than a copy of it: there is no source to
read, so a row emits one MEMW write and no read (half the memory traffic per
byte), and every byte written is the same constant, so one `fill` column
replaces memcpy's eight value lanes. `fill_wide` is `fill` on eight-byte rows
and zero on one-byte tail rows, which lets one write tuple serve both widths.
`fill <= 255` is proven on the first row; the executor rejects wider values and
the guest stub masks a1, mirroring how the byte-count bound is handled.

Measured on real mainnet block 25368371 (50,781,394 cycles baseline):
  #874 memcpy alone   41,642,609  -17.99%
  + memset (this)     40,338,153  -20.57%
mem* routines fall from 24.41% to 4.84% of guest cycles.

No existing AIR changes: CPU stays at 38 columns and the new table only adds
senders to existing buses.
The DMA memcpy ecall already snapshots its entire source range before writing
(all reads at T+1, all writes at T+2), so one chunk has memmove semantics for
free. Chunking is what breaks it: copying [0,256) -> [4,260) clobbers source
bytes a later forward chunk still needs.

So the memmove stub walks chunks from the END backwards exactly when the
destination starts inside the source range (src < dst < src+n); every chunk
then reads bytes no earlier chunk has written. Disjoint regions, and dst below
src, keep forward chunking.

This costs one guest symbol and nothing else — no table, no syscall, no
constraint. Measured on real mainnet block 25368371:
  memcpy + memset      40,338,153
  + memmove (this)     39,867,443   -0.93%
Cumulative vs the 50,781,394 baseline: -21.49%.

The guest test covers both overlap directions at offsets either side of the
256-byte chunk boundary, plus exact aliasing.
@diegokingston

Copy link
Copy Markdown
Collaborator Author

/bench

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

Benchmark Results for modified programs 🚀

Command Mean [ms] Min [ms] Max [ms] Relative
head ecsm 3.4 ± 0.0 3.4 3.5 1.00
Command Mean [ms] Min [ms] Max [ms] Relative
head hashmap 118.4 ± 4.1 113.6 125.7 1.00
Command Mean [ms] Min [ms] Max [ms] Relative
head keccak 130.5 ± 3.9 124.0 134.7 1.00
Command Mean [ms] Min [ms] Max [ms] Relative
head syscall_commit 94.8 ± 1.0 92.6 95.7 1.00

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

Benchmark — real block (ethrex_mainnet_25368371.bin) (median of 3)

continuations · epoch 2^22 · 10 epochs

Metric main PR Δ
Peak heap 48519 MB 52081 MB +3562 MB (+7.3%) 🔴
Prove time 166.381s 129.635s -36.746s (-22.1%) 🟢

🎉 Improvement on the real block — prove time down 22.1%.

Prove-time spread 1.9% (129.635s / 128.678s / 131.142s)

Commit: 77a5467 · Baseline: cached · Runner: self-hosted bench

@Oppen

Oppen commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Automated review pass (high-effort, adversarially verified). Scoped to this PR's own diff on top of 874's current head. Findings:

  • risk: prover/src/lib.rs:86 FIXED_TABLE_COUNT 11→12 for the new DMA_SET table, same shape as 876's HINT table — generate_dma_set_trace pads to .max(4) rows unconditionally, no recursion-verifier/no-memset-baseline measurement in the PR (only guest-cycle numbers for a memset-heavy workload). Third instance of this pattern across the current review batch (874, 876, 896).
  • risk: executor/src/tests/dma_tests.rs:150 memset happy path never tests n == 256 (DMA_MEMCPY_MAX_BYTES) — only n == 257 as an error case, proptest caps at 0..200. Memcpy's own proptest does cover the n=256 boundary; add the matching case for memset.
  • nit: executor/src/vm/instruction/execution.rs:522 DmaMemset's dst.checked_add(n) copied verbatim from memcpy, one byte over-conservative (false rejection at dst=u64::MAX-7, n=8, not a soundness issue). Worth tightening to checked_add(n-1) while touching this arm.

Soundness of the new DMA_SET table itself and the memmove-via-memcpy reroute (overlap-direction correctness) both came back clean under adversarial review — no forgeable bus interaction, no wrong-byte case found.

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.

2 participants