Skip to content

perf(thumbnails): reuse a decode the live preview cache already holds - #7

Closed
thetalkingdrum wants to merge 1 commit into
mainfrom
claude/great-feynman-7tke63-live-cache-peek
Closed

thetalkingdrum wants to merge 1 commit into
mainfrom
claude/great-feynman-7tke63-live-cache-peek

Conversation

@thetalkingdrum

Copy link
Copy Markdown
Owner

Part of marcinz606#1232. ThumbnailRenderWorker decodes through its own PreviewManager (thumbnail_render_preview_service) and never consults the live preview_service, so a frame the canvas or the neighbor prefetch just decoded is read over the network again: 15–20 s per frame on the reporter's SMB share.

The gain is bounded. preview_cache_max_entries = 8 and the open frame is already excluded from the refresh, so on the reporter's 49-frame roll this saves at most ~7 reads. The bulk of the time stays on I/O. This is a cheap win, not a fix for slow shares.

Changes

  • PreviewBufferCache.peek(key): like get, under the lock, but leaves the LRU order unchanged.
  • _linear_preview_key(...): one module-level helper in preview_manager.py. All six identical inline PreviewCacheKey(...) builds for a plain-frame decode now use it, including the put site, so a stored decode and a later lookup can't drift apart. Pure refactor; every key field is unchanged.
  • PreviewManager.peek_linear_preview(...): builds the key with the helper and returns self._cache.peek(key). It never decodes and never puts.
  • Worker lookup: ThumbnailRenderWorker(preview_service, live_preview_service=None); the controller passes self.preview_service. For plain frames only (not stitch, HDR or RGB triplet, using the decode paths' own predicates), the worker peeks the live cache first and falls back to the private service on a miss. The live cache is read-only here: a roll-wide refresh never evicts or reorders the navigation cache.
  • No shared-buffer writes: on a hit the buffer and every ndarray in its metadata are np.copy'd before run_pipeline. That's milliseconds against a network read, and it avoids having to prove the CPU stages never write in place.
  • Logging: one logger.debug hit/miss line per frame, so real-world hit rates can be checked.

Key parity (where hits can silently fail)

  • Half-frame assets and roll forks are cached by the live path under the asset hash plus half_slice. The thumbnail path looks up base_hash(...) with no slice and slices afterwards, so those entries are never found. Accepted for now; the keys are not unified in this PR.
  • Decode params: the live path takes use_camera_wb, positive_source, highlight_mode, bake_camera_wb, demosaic and lens_* from the raw saved settings, while the refresh uses the resolved config with roll defaults applied. When they differ, a miss is correct, because the cached decode was made with other params.
  • Only full_resolution=False entries can match, which is also correct.

Tests

  • peek leaves the LRU order unchanged; get still reorders; an absent key misses.
  • peek_linear_preview hits a decode cached by load_linear_preview and misses on a different WB or hash, without calling the loader.
  • The worker uses a live hit without decoding and hands the pipeline copies (no shared memory with the cached arrays), falls back to the private service on a miss, and skips the peek for a stitch.
  • Key parity: for a plain whole frame with default settings, a decode cached under the key built from _neighbor_prefetch_task(...) is found by the refresh's lookup.
  • The existing test_uses_a_private_preview_cache still passes, and the controller wires the live service in.

make all: lint and type clean, 7326 passed. A later docstring-only change was checked with lint and format.

Still to check (manual)

Open a roll, step through 5–6 frames, then press Update Thumbnails. The debug log should show hits for the visited and prefetched frames and misses for the rest.

🤖 Generated with Claude Code

https://claude.ai/code/session_01W1MEH5L17tDmpmz1H47dap


Generated by Claude Code

Update Thumbnails decodes through its own PreviewManager, so a frame the
canvas or the neighbor prefetch had just decoded was read from the source
again; on a slow network share that is a 15-20 s read per frame
(marcinz606#1232). The gain is bounded: the live cache holds at most
preview_cache_max_entries (8) decodes and the open frame is excluded, so
at most ~7 of a 49-frame roll.

- PreviewBufferCache.peek: a lookup that leaves the LRU order unchanged.
- _linear_preview_key: every plain-frame put and lookup in PreviewManager
  builds its key here (pure refactor of six inline copies).
- PreviewManager.peek_linear_preview: never decodes, never puts.
- ThumbnailRenderWorker takes the live service and peeks it first for plain
  frames (not stitch, HDR or RGB triplet), copying the buffer and metadata
  arrays so the pipeline never writes into the navigation cache; a miss
  falls back to the private cache as before. Hits and misses log at debug.
- Half-frame assets and roll forks key differently on the live path and
  always miss; decode params that differ between saved settings and the
  resolved roll config miss correctly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W1MEH5L17tDmpmz1H47dap
@github-actions

Copy link
Copy Markdown

Preview load metrics (github-linux-x64)

format cold load throughput
CR2 0.790 s 28.41 MPx/s
NEF 0.659 s 37.30 MPx/s
ARW 0.231 s 87.22 MPx/s
RAF 2.326 s 6.99 MPx/s
DNG 0.688 s 34.82 MPx/s

fixtures: rawsamples.ch · sha: 3704b21 · full JSON

@thetalkingdrum
thetalkingdrum marked this pull request as ready for review September 30, 2026 20:28
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.

2 participants