boxblur: replace the per-pixel division with an exact reciprocal - #179
Merged
Conversation
Both passes end in `acc/size`. `size` is a runtime value, so that compiled to a real integer division: ~20-30 cycles inside `boxblur_hori`'s serial running sum, and in `boxblur_vert` it kept the output loop from vectorizing at all, since no SIMD unit has an integer divide. Replaced by `(acc*mul) >> shift` with `mul = ceil(2^shift/size)`. The constants are checked rather than assumed: `acc <= 255*size` forces the 32-bit product to cap `shift` at 24, and exactness then needs `255*size*(size-1) < 2^24` -- true for every odd size up to 265, first failing at 267. So `vs_reciprocal()` searches for a shift satisfying both the overflow and the exactness condition and reports failure if there is none, in which case both passes keep the division. (`boxblurPlanar` only ever passes stepSize-derived sizes, far inside the range.) Output is bit-identical. `test_boxblur` now also checks `boxblur_hori_C` against a verbatim copy of the original, which was untested, and the size list straddles 265/267 so the reciprocal and the division fallback are both exercised -- 500 -> ~1200 checks. Ryzen 9 9900X, 1080p, size=15, best of three, ms/frame: boxblur_hori 2.23 -> 1.15 (1 thread) 0.22 -> 0.11 (24 threads) boxblur_vert 2.35 -> 0.44 (1 thread) 0.88 -> 0.27 (24 threads) Co-Authored-By: Claude Opus 5 (1M context) <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.
Both boxblur passes end in
acc/size.sizeis a runtime value, so that compiled to a real integer division — ~20-30 cycles insideboxblur_hori's serial running sum, and inboxblur_vertit kept the output loop from vectorizing at all, since no SIMD unit has an integer divide.This was listed under "Known limits" in
docs/simd.md, with the claim that a magic-number reciprocal is "provably exact forsize <= 4096with a 32-bit multiply". That bound is wrong.acc <= 255*size, so the product only fits in 32 bits ifshift <= 24; exactness then needs255*size*(size-1) < 2^24, i.e. it holds for odd sizes up to 265 and fails first at 267.So the constants are checked rather than assumed.
vs_reciprocal()searches downward fromshift=31for a shift satisfying both the overflow and the exactness condition, and returnsvalid=0if none exists — in which case both passes keep the division.boxblurPlanaronly ever passes stepSize-derived sizes, so the fast path is always taken in practice.Applied to
boxblur_horitoo, where the division sat in the serial chain; that turned out to be the larger absolute win.Correctness
Output is bit-identical.
sizein 1..8192 × every reachableacc,(acc*mul)>>shift == acc/size. No mismatch.boxblur.c's vertical output loop now reportsloop vectorized using 16 byte vectorsunder-fopt-info-vec; it did not before.tests/test_boxblur.cnow also checksboxblur_hori_Cagainst a verbatim copy of the original — it was previously untested — and the size list straddles 265/267 so both the reciprocal and the division fallback are exercised. 500 → ~1200 checks.Measurements
Ryzen 9 9900X, 1080p,
size=15, best of three (bench/bench_boxblur.c):boxblur_horiboxblur_vert2.9x on the blur in both configurations.
🤖 Generated with Claude Code