perf: vectorize image and bitplane encoders#93
Merged
Conversation
Replace the per-pixel Python loops in encode_1bpp/encode_2bpp/encode_4bpp and encode_bitplanes/encode_gray4_bitplanes with numpy packbits/LUT operations. These loops were 60-73% of prepare_image() CPU; the vectorized versions are ~100-1000x faster (e.g. encode_1bpp @800x480: 45ms -> 42us) and were verified byte-identical to the originals across widths not divisible by 8/4/2 (122, 250, 799, 127, 1) and the full 0-255 palette-index range. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JRrm95f1qNZzDM9r2SB6KW
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
3 tasks
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.
Summary
Replaces the per-pixel Python loops in all image/bitplane encoders with vectorized numpy operations (
np.packbits+ LUTs). Profiling showed these loops were 60–73% ofprepare_image()CPU.Affected functions:
encode_1bpp,encode_2bpp,encode_4bpp(encoding/images.py)encode_bitplanes,encode_gray4_bitplanes(encoding/bitplanes.py)Measured speedups (median, current → vectorized @800×480):
encode_1bppencode_2bppencode_4bpp(grayscale16)encode_4bpp(bwgbry)encode_bitplanesencode_gray4_bitplanesFull
prepare_imagepipeline ends up ~2.3–2.9× faster overall; afterwards the Rust dither step dominates, which is where the time should be.Correctness
The vectorized versions were verified byte-identical to the original loop implementations across widths not divisible by 8/4/2 (122, 250, 799, 127, 1), several heights, and the full 0–255 palette-index range — including the BWGBRY value map
{0,1,2,3,5,6}(with its.get(…, 0)default preserved) and multiple gray4 code tables.packbits(axis=1)reproduces the existing per-row byte-boundary zero-padding exactly.Test plan
uv run pytest -q→ 439 passedruff check,ruff format,mypy src/clean🤖 Generated with Claude Code