Bound decoded-image cache memory and deprioritize ingest threads - #1648
Open
reville wants to merge 2 commits into
Open
Bound decoded-image cache memory and deprioritize ingest threads#1648reville wants to merge 2 commits into
reville wants to merge 2 commits into
Conversation
The decoded-image cache previously evicted by item count alone and held every image as f32, so a few large RAWs could pin several gigabytes and push lower-RAM machines into swap (CyberTimon#515, CyberTimon#521). Cache entries are now stored at half precision (the GPU pipeline already samples f16, so rendered output is unchanged) and eviction also enforces a byte budget derived from physical RAM (total/8, clamped to 256 MB - 4 GB). Thumbnail and metadata workers now run at Utility QoS on macOS so folder ingest yields to the UI and other apps instead of saturating performance cores; this is a no-op on other platforms. is_image_cached uses a new non-mutating contains() instead of get(), so a boolean probe no longer reorders LRU eviction or rebuilds pixels. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The f16 round-trip test used a square image, so a transposed width and height would have passed. Cover a non-square image in both orientations, assert the restored dimensions, and add an RGBA case. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Aug 30, 2026
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.
Description
The decoded-image cache evicted by item count alone and stored every entry as f32, so a handful of large RAWs could pin several gigabytes. A 48.8 MP image costs 559 MB once decoded to
Rgb32F, and the default cache holds 5, so the cache alone can reach 2.9 GB before anything else is accounted for. On unified-memory Macs that also starves the GPU, which matches the reports in #515 and #521.Three changes:
Rgba16Floattextures, so rendered output is unchanged. Images that are not f32 are stored untouched.set_utility_thread_qosis a no-op on other platforms.is_image_cachednow calls a non-mutatingcontains(). It previously calledget(), so a boolean probe reordered LRU position and, under this change, would have rebuilt pixels for a question that only needed a yes or no.Type of Change
Changes Made
cache_utils.rs:DecodedImageCachestoresCachedImage(f16 pixels plus dimensions and byte size), tracks total bytes, and evicts on both limits. Addscontains()and unit tests.image_loader.rs:is_image_cachedusescontains().file_management.rs: addsset_utility_thread_qos, called by the thumbnail and metadata workers.Testing
Measured on the hardware below, comparing this branch against
mainat 7ac8d50.Cache footprint, inserting seven 48.8 MP images into a 5-entry cache and reading
ri_phys_footprint:mainApplication run, same 7-image folder opened, walked through, and revisited in both builds:
mainThe cost. A cache hit is no longer free, because pixels are rebuilt from f16:
So a hit still avoids roughly 95% of the work a miss costs, and the 8 ms lands when switching to a previously viewed image, not during editing. The image being edited is held separately in
original_imageand never round-trips through the cache.Seven unit tests cover LRU order, byte-budget eviction, the oversized-image case, f16 round-trip accuracy, non-f32 passthrough, and QoS assignment. The round-trip tests use non-square images in both orientations and assert restored dimensions, so a transposed width and height cannot pass.
Cross-platform: the only platform-specific code is
set_utility_thread_qos. I verified the non-macOS branch compiles by forcing it active on macOS. Everything else uses existing unconditional dependencies (half,rayon,sysinfo,image). I could not build Windows or Linux locally, so CI is the real check there.Test Configuration:
Checklist
cargo fmt --checkandcargo clippyare clean on the changed files. The twoclippywarnings inexif_processing.rsare pre-existing onmain.Additional Notes
The byte budget is deliberately a fraction of RAM rather than a user setting, so the existing
imageCacheSizepreference keeps its meaning as a maximum. If you would rather expose the budget in settings, or pick a different fraction, I am happy to change it.I left the two f16 conversions on rayon. They are ~22 ms on insert and ~8 ms on read for a 48.8 MP image, and both already run off the UI thread.
AI Disclaimer:
Please state the involvement of AI in this PR:
Written by Claude Code, directed and reviewed by the repository owner of the fork.