DrawImage: stop re-proving what a repeat already proved, and keep rowOpaque out of line - #13
Merged
Merged
Conversation
…Opaque out of line Two defects, both found by measuring the widgets rather than the primitive, and both invisible from reading the code. v0.7.0 regressed the unclipped cases it was not meant to touch: a full 1000x700 blit went from 265,039 ns/op at v0.5.0 to 1,152,535. The clipped rewrite made the loop bigger, and rowOpaque -- a tight strided scan whose cost lives entirely in register allocation -- was inlined into it. Kept out of line the SAME code measures 238,252. The directive carries that pair of numbers, because a reader deleting it deserves to know what it costs. Profiling pointed at rowOpaque while attributing memmove's work to it; only crossing the two implementations against each other, then the two inlining choices, settled which of the two was to blame. Separately, an enlarged blit scanned every source row TWICE: once when the row was built, once for each destination row repeating it -- 700 scans of 1.4 MB per blit to prove 350 rows opaque. Settling the repeat before touching the source removes that half outright. prevSY only ever holds a row that was written whole, so a match already proves opacity. 1:1 279726 ns/op (per-pixel 1867584) 6.7x enlarged 351219 ns/op (per-pixel 2066103) 5.9x clipped 314708 ns/op (per-pixel 2644680) 8.4x Every shape is now faster than it was at any earlier version: v0.6.0 blit scaled in 434,038 and v0.7.0 blit clipped in 911,278. 100% statement coverage; the 16-case comparison against the loop DrawImage replaced is unchanged and still passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two defects, both found by measuring the widgets rather than the primitive, and both invisible from reading the code.
v0.7.0 regressed the cases it was not meant to touch
A full 1000×700 blit went from 265,039 ns/op at v0.5.0 to 1,152,535. The clipped rewrite made the loop bigger, and
rowOpaque— a tight strided scan whose cost lives entirely in register allocation — got inlined into it. Kept out of line, the same code measures 238,252.The
//go:noinlinedirective carries that pair of numbers in its comment, because whoever deletes it deserves to know what it costs.Worth recording how this was found: pprof blamed
rowOpaquewhile quietly attributingmemmove's work to it, and the bounds-check dump was identical between versions. Neither told the truth. What settled it was crossing the two implementations against each other on the same machine back to back, then crossing the two inlining choices — a control run, not a reading of the profile.An enlarged blit scanned every source row twice
Once when the row was built, once again for each destination row repeating it: 700 scans of 1.4 MB per blit to prove 350 rows opaque. Settling the repeat before touching the source removes that half outright.
prevSYonly ever holds a row that was written whole, so a match already proves opacity — there is nothing to re-check.Result
Every shape is now faster than at any earlier version — v0.6.0 blit scaled in 434,038 and v0.7.0 blit clipped in 911,278.
100% statement coverage. The 16-case comparison against the loop
DrawImagereplaced is unchanged and still passes.🤖 Generated with Claude Code