Skip to content

perf: the steady tone is worked out once and read back - #57

Merged
donislawdev merged 1 commit into
mainfrom
perf/tone-computed-once
Sep 6, 2026
Merged

perf: the steady tone is worked out once and read back#57
donislawdev merged 1 commit into
mainfrom
perf/tone-computed-once

Conversation

@donislawdev

Copy link
Copy Markdown
Owner

Fourth chunk of the 2026-09-05 performance report: P3.

Section 4 of the verdicts said the report's 1.55x was not reproduced here. It is real, and larger.

What the sine actually cost, measured without writing any code

content=silence takes the same path without the sine, so the difference between them is the sine. On a 256 MB file, interleaved:

tone     min 1460  med 1495  max 1696 ms
silence  min  828  med  953  max 1074 ms

542 ms of 1495, or 36% of the run, ranges disjoint.

The fix, and why it beat its own upper bound

A tone repeats, so one cycle is worked out and read back. The cycle is rate/gcd(base, rate) frames - exact, because the pitch is a whole number of hertz.

Measured with the new tools/probes/abcpu, 256 MB, 7 reps, order reversed:

old new
user cpu min 1000 med 1172 max 1219 ms min 500 med 578 max 719 ms
wall min 1350 med 1360 max 1388 ms min 794 med 800 max 821 ms

2.03x less CPU, 1.70x less wall clock, disjoint on both. That 1.70x exceeds the 1.57x the measurement above implied, and not by accident: the table also removes a float division per frame, which silence was paying too and so could not reveal.

Breaking, but far narrower than announced

Reading the table back is not bit for bit what recomputing gives. But the gap lands far below the step between neighbouring sample values:

bit depth result
16 (default) 0 changes across 40 combinations of seed, size, rate, channels
24 and 32 25 changes of 30 combinations, ~1 sample in 20 000

A default WAV is byte for byte what it was. silence, noise and sweep are untouched.

Two blind spots, both found by measuring rather than reading

No golden case set bit_depth at all, so the only path this change touches had no pinned witness - the whole set would have gone green through it. Two cases added: wav_past_one_cycle (16-bit, holds the default to what it was, not a repin) and wav_32bit_past_one_cycle (the witness).

The first version of that witness was useless and I only found out by measuring it. I picked 512 KiB reasoning it was "more than a cycle" - at the pinned seed the cycle is 14700 frames, so it wraps four times over, and it differs in zero bytes:

512 KiB    0 differing bytes      2 MiB    250
1 MiB     38                      4 MiB   1048

It would have been green whatever the table did. Hence 2 MiB, with room, not just over the line.

A correction I had to make to my own comment

I first wrote that short files are unchanged because they never wrap. False - the seed picks pitches from 220 to 1099 Hz, so the cycle runs from 42 to 44100 frames, and 374 of 880 pitches wrap inside even a 32 KiB file. Quantisation is what holds them, not length. The mutation showed this by reddening wav_32kib.

wav.go split, because the ratchet asked for a split

The change put wav.go into the crowding band and writeSamples over the function threshold. signal.go now answers one question - what is the value of frame n - and wav.go keeps how many frames fit and how they reach the disk. Both crowding counters are back at their recorded values, so no ceiling was raised.

The mutation pattern went stale in the same step (tonePeriod moved files); staleness.py caught it in seconds.

Verification

  • full go test -tags "$(cat .github/build-tags)" ./... - green, 83 packages
  • python tools/preflight.py --quick - all 12 checks pass
  • try-named.py TestOurOwnGeneratorsHaveNotDrifted - 11 mutations, all caught
  • staleness.py - 798 patterns, each occurring exactly once

🤖 Generated with Claude Code

A tone repeats, so calculating sin for every sample was recomputing a
number the file already held. It is now worked out for one cycle -
rate/gcd(base, rate) frames, which divides cleanly because the pitch is
a whole number of hertz - and read back for the rest.

Measured on a 256 MB file, seven repetitions, order reversed, ranges
disjoint on both: 2.03x less processor time, 1.70x less wall clock. The
sine alone was 36% of the run, measured without writing any code by
timing content=tone against content=silence.

BREAKING, but far narrower than the report implied. Reading the table
back is not bit for bit what recomputing gives, because math.Sin reduces
a large argument differently. That difference lands far below the step
between neighbouring sample values, so at the default 16 bit depth it
never reaches the file: 0 changes across 40 combinations of seed, size,
rate and channel count. At 24 and 32 bits it does: 25 of 30. A default
WAV is byte for byte what it was.

Two golden cases, because bit_depth appeared in none of them, so the one
path this change touches had no pinned witness at all. wav_past_one_cycle
holds the default depth to what it was and is not a repin.
wav_32bit_past_one_cycle is the only witness to the change - at 2 MiB,
because 512 KiB differs in zero bytes and the first version of this case
sat there looking like coverage.

wav.go is split: signal.go answers what the value of frame n is, wav.go
stays with how many frames fit and how they reach the disk. The crowding
ratchet asked for the split rather than a larger number, and after it both
counters are back at their recorded values.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@donislawdev
donislawdev merged commit 9b087d5 into main Sep 6, 2026
18 checks passed
@donislawdev
donislawdev deleted the perf/tone-computed-once branch September 6, 2026 04:32
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