Skip to content

perf: size the read chunk to the probe's contact window - #17

Merged
jackulau merged 1 commit into
mainfrom
read-chunk-fits-contact-window
Jul 17, 2026
Merged

jackulau merged 1 commit into
mainfrom
read-chunk-fits-contact-window

Conversation

@jackulau

Copy link
Copy Markdown
Owner

Measured first, rather than assuming

USB traffic at 121 KiB/s SPI : 246 KiB/s = 2.01 Mbit/s
USB full-speed ceiling       : 12 Mbit/s  ->  we use 17%
SPI bits/s at 121 KiB/s      : 0.99 Mbit/s

USB is not the limit. The ~1 MHz SPI clock is.

The headroom I am deliberately NOT taking

We never configure the CH341A's stream clock. Init sends only CMD_UIO_STREAM pin direction; there is no stream-speed command anywhere in the backend, so we run at the device's power-on default. That is genuine unexploited headroom.

It is left alone on purpose:

  1. The value is unmeasured, and this project has a hard-won lesson about theorising from reference source instead of benching (a CS-deasserted bench needs no chip contact and is free and safe, so it should come first).
  2. A faster SPI clock degrades signal integrity on exactly the marginal probe this whole effort exists for. Speed and contact are in direct tension here. Shipping a clock change blind could make reads worse while looking like an optimisation.

Worth doing, with a bench, on real silicon. Not worth guessing.

The number that actually matters

Throughput is not the binding constraint. A chunk only counts if it finishes inside ONE unbroken contact window. At 121 KiB/s, with backup chunks read twice:

chunk contact needed
64KB 1.058s
32KB 0.529s
16KB 0.264s
8KB 0.132s
4KB 0.066s

A probe that cannot hold for 1.06s makes zero progress at 64KB, no matter how many windows it gets: every chunk dies partway and is discarded. That is a cliff, not a slope, and no clock speed climbs it. Measured contact on the board is 1 sample in 32.

So: ratchet read --chunk-kb N, default 64 (no behaviour change unless asked for).

Changing it restarts the dump. The sidecar records the stride and refuses to mix two griddings of one chip; that rejection is pre-existing paranoia and it is right here, because bytes banked at one stride were never verified at another.

The test was weak and mutation testing caught it

The first version asserted against a ResumeState the test itself constructed. It proved nothing about the read, and stayed green when set_read_chunk was mutated to ignore its argument: a decorative test for a decorative knob.

Rewritten to observe the grid the read wrote, via the sidecar it saves on failure. Both mutations now fail it:

Mutation Result
set_read_chunk ignores its argument FAILS (was: passed)
read loop uses the const instead of the field FAILS

CLI bounds verified end to end: --chunk-kb 99999 and --chunk-kb 0 both rejected.

Verification

cargo test --workspace 501 pass, clippy --workspace --all-targets -D warnings clean, fmt --check clean.

Measured where a 32MB read actually spends its time, rather than assuming:

  USB traffic at 121 KiB/s SPI : 246 KiB/s = 2.0 Mbit/s of a 12 Mbit/s
                                 full-speed ceiling, so 17% used
  SPI bits/s at 121 KiB/s      : 0.99 Mbit/s

USB is not the limit. The ~1 MHz SPI clock is, and we never configure it
(init sends only CMD_UIO_STREAM pin direction, no stream-speed command), so
we run at the device's power-on default. That is real headroom and it is left
alone deliberately: the value is unmeasured, and a faster clock degrades
signal integrity on exactly the marginal probe this is all for. It wants a
CS-deasserted bench on real silicon, not a guess from reference source.

The number that actually decides whether a marginal probe makes progress is
not throughput at all: a chunk only counts if it finishes inside ONE unbroken
contact window. At 121 KiB/s, with backup chunks read twice:

  64KB -> 1.058s of held contact      8KB -> 0.132s
  32KB -> 0.529s                      4KB -> 0.066s
  16KB -> 0.264s

A probe that cannot hold 1.06s makes NO progress at 64KB, however many
windows it gets: every chunk dies partway and is discarded. That is a cliff,
not a slope, and no amount of clock speed climbs it. Measured contact on the
board is 1 sample in 32.

So make it tunable: `ratchet read --chunk-kb N`, default 64 (unchanged).
Changing it restarts the dump, because the sidecar records the stride and
refuses to mix two griddings of one chip. That rejection is the existing
paranoia and it is correct here: bytes banked at one stride were never
verified at another.

Test observes the grid the READ wrote, via the sidecar it saves on failure.
The first version of this test asserted against a ResumeState the test itself
built, and stayed green when set_read_chunk was mutated to ignore its
argument: it proved nothing about the read. Both mutations now fail it
(setter ignores its argument; read loop uses the const instead of the field).
@jackulau
jackulau merged commit 2180420 into main Jul 17, 2026
1 check passed
@jackulau
jackulau deleted the read-chunk-fits-contact-window branch July 17, 2026 02:36
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