plist reads aren't cached, Organize_viewMode read ~440 times per second
- 0 replies
- 10 views
- Translate
Description of Issue
- While culling, Lightroom reads its preferences through NSUserDefaults ~750 times per second (peaks above 1,800/s), 99% of them on the main thread. Idle, it reads none.
- One key dominates: Organize_viewMode is read ~440 times per second (62% of all reads). The 10 most-read names make up 94.5%.
- Lua code also assigns preferences ~7 times per second while culling (Organize_lastLocalAssetSelection and Organize_lastLocalActiveAssetSelection on every selection change, AgDevelop_denoiseAmount, lastOzUserId), but almost all assignments store the value already there: only 24 actual writes reached CFPreferences in 207 s. Each assignment still reads the current value first.
- 93% of the reads come from two Lua bindings in AgSubstrate that read a preference with no cache: each takes the key from the Lua stack, calls [[NSUserDefaults standardUserDefaults] objectForKey:], converts the value and pushes it back to Lua (AgSubstrate+0x1ab48 and +0x1ade0, arm64). So every preference access in Lua code is a round trip into CFPreferences.
- Each lookup is cheap (~0.33 µs here), so the steady cost is small, but it's all on the main thread, it scales with UI activity, and it grows ~50× while a debug log stream is attached (each lookup logs a debug message).
Step-by-step Reproduction Instructions
1. Launch Lightroom and open a large raw shoot in Detail view (D).
2. In Terminal, record the preference traffic for 60 seconds while you cull (arrow keys, Z / X):
log stream --level debug --style compact --predicate 'process == "Adobe Lightroom" AND subsystem == "com.apple.defaults"' > prefs.log & sleep 60; kill %1
3. Count lookups (each lookup logs one "looked up value" line) and see which thread makes them:
grep -c "looked up value" prefs.log
grep "looked up value" prefs.log | grep -o "\[[0-9]*:[0-9a-f]*\]" | sort | uniq -c | sort -rn | head -3
4. Repeat step 2 with Lightroom idle in the background for comparison.
5. Optional, to see the callers (Xcode installed): while step 2's log stream is running, record
xcrun xctrace record --template "System Trace" --attach $(pgrep -x "Adobe Lightroom") --time-limit 20s
Each lookup's debug message makes a few system calls while the stream is attached, so the trace's system-call stacks show the frames above -[NSUserDefaults objectForKey:].
6. Optional, to see which keys (keys are <private> in the unified log): allow -runLua in config.lua (atYourOwnRisk.runLuaCLI = true), launch Lightroom with -runLua and Lua code that wraps getmetatable(import 'AgPreferences'.user).__index / __newindex to count keys, and print the counts every 30 s. The code used here is in the attachment (5-keys-by-window.txt).
Expected Result
- Organize_viewMode is read when the view mode changes (or observed), not ~440 times per second.
- Lua code doesn't round-trip into NSUserDefaults hundreds of times per second on the main thread. AgPreferences caches values in the Lua layer and invalidates them when they change (NSUserDefaultsDidChangeNotification or its own setters), or hot paths read a preference once and keep it.
- Code that assigns a preference on every selection change skips the assignment when the value hasn't changed.
Actual Result
- 2026-11-01 22:26–22:33 (UTC-6), 207 s of culling in Detail view, log stream over the whole window:
- 154,900 lookups by Adobe Lightroom (748/s; median 713/s, max 1,852/s over 205 seconds). 153,427 (99.0%) on the main thread (0x1957fd8, "Main Thread" in the trace).
- For comparison, the busiest other process in the same window made 6.4 lookups/s (spotlightknowledged.updater), the next 2.2/s.
- ~2,950 lookups (1.9%) found no value at all (each logs "found no value" twice, plus "Contents Need Refresh").
- 24 preference writes ("setting … in CFPrefsPlistSource") in the whole window
- Lightroom idle in the background: 0 lookups in 20 s.
- Keys, counted inside Lightroom (wrapping AgPreferences.user's metatable from a -runLua probe; key names only), 2026-11-01 23:30–23:37, 7 minutes of culling in Detail view, 17,000–28,000 reads per 30 s (580–935/s):
- Organize_viewMode 185,101 (61.7%, ~440/s)
- valueForKeyPath 42,906 (14.3%; a method looked up on the preferences object)
- useAutoBahn 12,657 (4.2%), accountType 11,721 (3.9%)
- ColorMixerMode 6,960 and MixerPanelColorChoice 6,960 (read together), AgCommonUI_backgroundShade 6,714
- gpu4setting 3,896, showHistogram 3,849, AgDevelop_targetPanelContent 3,048, then nine AgDevelop_cropCycle_* crop-overlay settings at ~550 each
- the top 25 names cover 99.4% of reads
- Assignments in the same 7 minutes (top 10 per window, summed): Organize_lastLocalAssetSelection 841, Organize_lastLocalActiveAssetSelection 841, AgDevelop_denoiseAmount 714 (no editing was done), lastOzUserId 340, AgLibrary_loupeZoomLevel 104.
- System Trace, 20 s of the same session, callers of -[NSUserDefaults objectForKey:] (lookups ≈ system calls / 3):
- 10,901 AgSubstrate+0x1ab48: Lua read binding (key from the Lua stack → objectForKey: → AgLua_pushValue), called via lua_call
- 2,206 AgSubstrate+0x1ade0: a second Lua read binding with the same pattern
- 416 AgPreferences_storeValue: reads the current value before writing
- 302 WichitaFoundation+0x2e9f9c (near WFModelAssignToProperty), called from Lua
- 153 AgUI (3 call sites near duskDisabledTextColor), called from Lua
- 82 macOS itself (HIServices _AXInterfaceGetReduceMotionEnabled / _AXInterfaceCursorIsOverridden)
System Details
- Lightroom 9.6 (Mac App Store), build 20260923-1659-a32d844, arm64
- macOS 27.0.1 (26A434), MacBook Pro 16" (MacBookPro18,2), Apple M1 Max, 64 GB RAM
- Cameras: Canon R5m2 and R6m3
- Library: 41,490 photos, 211 albums, 4,818 stacks; local library 78–85 GB (40 GB originals, 29 GB proxies, 12 GB previews); all proxies stored locally, photo cache limit 50 GB
- Working set: an 11,650-photo Canon CR3 shoot
