Skip to content

perf(photos): read the stored frame upright inside the resample - #891

Merged
oddharsh merged 1 commit into
mainfrom
claude/zenc-resample-oriented-v2
Sep 22, 2026
Merged

oddharsh merged 1 commit into
mainfrom
claude/zenc-resample-oriented-v2

Conversation

@oddharsh

Copy link
Copy Markdown
Owner

Needs oddharsh/halflight#2 merged with a merge commit (not a squash), as halflight#1 was, so the pinned sha 871487e stays reachable from halflight's main. CI here already builds against it, since the sha is fetchable from its branch.

The second pass of the loop. #889 made the orientation copy fast; this removes it. zenc square now hands the stored frame to halflight's new resample_oriented, which reads it upright inside the horizontal pass and is bitwise equal to orienting first. No 311 MB upright copy is built for any rotated photo.

Measured

Same-file decode-to-resample floor, one 6240x4160 JPEG, three builds interleaved:

orientation before #889 #889 (tiled copy) this
1 (control) 508 ms 492 ms 491 ms
3 743 ms 568 ms 491 ms
6 770 ms 582 ms 445 ms
8 872 ms 592 ms 461 ms

Rotation now costs nothing, and the axis-swapping cases come in under upright, because each filter tap streams a whole stored row and updates thousands of accumulators in one contiguous loop.

bun run zenc:bench against #889's tree, which is main's zenc now:

source wall CPU p (wall / CPU)
XT507333.JPG, rotated 1.074x 1.038x 0.0006 / 0.0012
XT500010.HIF, rotated 1.064x 1.023x 0.0006 / 0.0006
XT507876.JPG, orientation 6 1.059x 1.032x 0.0006 / 0.0012
three upright sources 0.98-1.01x 0.99-1.00x not called

Gate: all 24 square outputs and 258 histograms byte-identical.

The loop, inside halflight

Measured in a probe on the kernel alone (6240x4160 RGB, three box tiers) before touching zenc:

  1. Blocked, 64 upright rows at a time: lost. 174/240 ms against 131/149 ms for perf(photos): orient full frames by row and by tile instead of per pixel #889's copy plus resample, because zenc resamples the full frame three times and a strided pass paid three times is worse than one tiled copy.
  2. Bigger blocks won, every doubling, until one block spanned the frame: 46/61 ms, against 59 ms to resample an already-upright frame. So the block was deleted, and every upright row accumulates at once.

Changes

  • tools/photos/zenc/Cargo.toml: halflight 80e9f34 → 871487e, with a note on how to move it.
  • square::tier takes the orientation and computes geometry on the upright size; pixels::scale_oriented wraps resample_oriented.
  • The two examples keep their own orient step and pass Upright, so they behave exactly as before.
  • resize and frame still use perf(photos): orient full frames by row and by tile instead of per pixel #889's tiled orient.
  • images/histograms regenerated from this build (byte-identical) and re-locked, since Cargo.lock and pixels.rs are its inputs.

Checks: cargo test 39/39, clippy -D warnings with and without heif-experiment, lint, test 847/0, derive:check.

zenc square oriented a full-frame copy (311 MB of f32 for a 26 MP photo) and
then resampled it three times. It now hands the stored frame to halflight's new
resample_oriented, which reads it upright inside the horizontal pass and is
bitwise equal to orienting first. Pins halflight at 871487e (its
resample-oriented branch, to be merged with a merge commit as #1 was).

Same-file decode-to-resample floor, 6240x4160 JPEG, interleaved:
orientation 1 491 ms (unchanged), 3 568 -> 491, 6 582 -> 445, 8 592 -> 461.
Rotation now costs nothing, and the axis-swapping cases beat upright because
each tap streams one whole stored row.

zenc:bench against the tiled-orient branch: identical on all 24 outputs and
258 histograms; rotated sources 1.06-1.07x wall and 2.3-3.8% less CPU, each
at p <= 0.0012; upright sources not called.

images/histograms regenerated from this build (byte-identical) and re-locked,
since Cargo.lock and pixels.rs are its inputs.
@github-actions github-actions Bot added area: docs Runbooks and repository prose area: infra Declared Cloudflare state, wrangler config, pins area: photos The photo pipeline and everything it emits type: perf Changes what something costs labels Sep 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Wire-size diff vs merge base

6917a59397266bfe5fc299230bdbb0136834319a → 30979bc8fcde5d28fdf835cd7585a7c0ea38b442

Brotli q11, the quality the edge serves. Every number here is deterministic:
an unchanged file produces no row. Advisory — this check fails on nothing.

Client assets

No change. 26 files, 87.55 KiB Brotli total.

Pages

No change. 57 files, 585.04 KiB Brotli total.

Worker bundle

No change. 250.16 KiB gzip.

Dictionary transport

Family dictionary acquisition: no change, 14.41 KiB q11.

pd/: no change. 113 deltas, 488.91 KiB total.

@oddharsh
oddharsh merged commit 05ad41b into main Sep 22, 2026
15 checks passed
@oddharsh
oddharsh deleted the claude/zenc-resample-oriented-v2 branch September 22, 2026 19:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: docs Runbooks and repository prose area: infra Declared Cloudflare state, wrangler config, pins area: photos The photo pipeline and everything it emits type: perf Changes what something costs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant