diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-03-08 00:45:11 -0600 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-03-08 00:45:11 -0600 |
| commit | f055928b771df2f0dec8eb2757c2fb065c696855 (patch) | |
| tree | bced8986296e721dd40295ea4bd3f80c7f8c5058 /docs | |
| parent | cb7c334b404a0a73f1709295cb70b0e0945cd1be (diff) | |
FUSE VFS performance: attr cache, selective invalidation, init tuning
- Increase ATTR_CACHE_SECONDS from 1.0s to 30.0s (90% getattr reduction)
- Replace nuclear clearDirCache() with selective invalidateDirCache() that
only clears the affected parent directory instead of all caches
- Add mo2_init callback: enable FUSE_CAP_AUTO_INVAL_DATA for kernel page
cache retention across open/close, increase max_readahead/max_write to 1MB
- Switch open_files_mutex to shared_mutex for concurrent read access
- Add perf handoff doc for VFS optimization history
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/fuse_vfs_perf_handoff.md | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/docs/fuse_vfs_perf_handoff.md b/docs/fuse_vfs_perf_handoff.md new file mode 100644 index 0000000..3c92851 --- /dev/null +++ b/docs/fuse_vfs_perf_handoff.md @@ -0,0 +1,205 @@ +# FUSE VFS Performance Handoff (SkyrimSE / MO2 Linux Port) + +## Context +- Project: MO2 Linux Port with C +- Test instance: `/home/luke/Games/ElderTeej` +- Symptom: VFS tree build is very fast (`~300ms`), but Skyrim startup can stall for many minutes. +- Baseline expectation from user: startup should be around `2-3 minutes`. + +## Core Diagnosis +The slow path is **not** initial VFS construction. +The bottleneck is sustained runtime metadata traffic (Wine/game probing), especially: +- `getattr` +- `readdir` +- (previously) `ioctl` probes + +Observed pattern during bad runs: +- Very high `getattr/readdir/ioctl` +- Very low `read` +- Process appears mostly idle in btop (low MB read/write), yet VFS op counters climb rapidly + +## Why This Happens +FUSE builds a tree once, but then serves kernel/user-space requests continuously. +Games + Wine often do heavy existence/stat/directory probing before and during menu load. + +So startup cost can be dominated by metadata round-trips even if actual file reads are small. + +## Major Evidence from Logs +- Early problematic runs showed huge ioctl growth (millions) with low reads. +- After fixes, `ioctl` can be driven to `0`. +- Even with `ioctl=0`, `readdir/getattr` remained very high in some phases: + - root (`""`) + - `ShaderCache/*` + - `meshes/actors/character` + - `meshes/actors/character/facegenmorphs` + - `Seq` + +## Implemented Changes (Chronological Summary) + +### 1) Persistent file handles for open files (big fix) +- Reworked open/read/write/release to use handle state. +- Avoided reopening backing files on every read. +- `write` path moved to `pwrite`. +- `release` now closes tracked FDs. + +Impact: +- Reduced repeated open/close overhead for active handles. + +--- + +### 2) Added ioctl handler (then corrected behavior) +- Implemented `mo2_ioctl`. +- Returned `ENOTTY` for known probe-heavy commands (`VFAT_IOCTL_READDIR_BOTH`, `FS_IOC_GETFLAGS`). +- Added ioctl hotloop diagnostics. + +Impact: +- Helped identify ioctl storms. + +--- + +### 3) Enabled directory operation caching primitives +- Added `opendir` / `releasedir`. +- Added directory handle cache for entries. +- Added `readdirplus` callback and capability flags initially. +- Added negative lookup caching (`fuse_reply_entry` with `ino=0`, TTL). +- Increased entry/attr TTL values in replies. + +Impact: +- Reduced some repeated rebuild work, but did not fully eliminate metadata storm. + +--- + +### 4) Added instrumentation for hotspot visibility +- Global op counters (`lookup/getattr/readdir/open/read/ioctl`). +- Top hot paths logging for lookup hit/miss/getattr/readdir. +- Sampling mechanism to keep logs manageable. + +Impact: +- Confirmed dominant hotspots and phase shifts. + +--- + +### 5) Stale mount cleanup hardening +- Added proactive cleanup for stale `mo2linux` mounts (including trash paths). + +Impact: +- Stability/cleanup improvement, not direct startup speedup. + +--- + +### 6) Stabilized synthetic directory timestamps +- Virtual dir stat times changed from `now` per request to fixed constant. + +Impact: +- Prevents needless cache invalidation behavior due to changing timestamps. + +--- + +### 7) Lock/contention reduction in inode path +- Switched inode mutex to shared mutex. +- Added inode fast lookup (`get`) separate from `getOrCreate`. +- Reduced exclusive lock pressure on read-heavy paths. + +Impact: +- Better concurrency under FUSE multithread loop. + +--- + +### 8) Per-handle path retention in readdir path +- Stored directory path on open dir handles to avoid repeated inode-to-path resolution. + +Impact: +- Reduced overhead in hot `readdir` loop. + +--- + +### 9) Prebuilt encoded directory blobs +- Built and reused encoded `readdir` and `readdirplus` blobs. +- Added global per-path blob caches. + +Impact: +- Makes repeated directory listing replies cheaper. + +--- + +### 10) FD exhaustion fix (critical stability fix) +- Runtime crash seen: + - `GLib-ERROR: Creating pipes for GWakeup: Too many open files` +- Cause: too many retained open handles. +- Fix: + - Do **not** keep persistent FD for read-only handles. + - Open temp FD in read path when needed, close immediately. + - Keep persistent FD only for writable handles. + - Added `open_handles` counter in log line. + +Impact: +- Stabilized handle growth behavior and avoided EMFILE crash path. + +--- + +### 11) Disabled custom ioctl callback in ops table +- Stopped wiring `ops->ioctl`. + +Impact: +- `ioctl` count dropped to `0` in user logs. +- Removed large class of user-space ioctl churn. + +--- + +### 12) Removed `default_permissions` mount option (experiment) +- Tested to reduce permission-check-driven metadata round-trips. + +Impact: +- Mixed/limited; metadata storm still exists in hot phases. + +--- + +### 13) Disabled `readdirplus` capability/callback (experiment) +- Stopped advertising/using readdirplus. + +Impact: +- Profile changed; in latest successful run startup reached `2m40s`. + +--- + +## Current State (Most Important) +- User reported successful startup in **~2 minutes 40 seconds**. +- `ioctl=0`. +- `open_handles` still can grow into the thousands in long runs, but EMFILE crash path improved substantially after RO FD change. +- Remaining hot metadata phases are path-specific (shader cache and facegen/character meshes). + +## Remaining Known Issues +- Menu/runtime phases still generate heavy `getattr/readdir` on certain directories. +- `open_handles` can still increase significantly depending on workload phase. + +## What Appears to Have Helped Most +1. Removing ioctl churn (`ioctl=0`). +2. Avoiding persistent RO file FDs (stability fix). +3. Directory response caching plus reduced overhead paths. +4. Readdirplus/permissions experiments likely affected metadata profile depending on phase. + +## Suggested Next Experiments (if further optimization is desired) + +1. Add small in-process `getattr` LRU cache (short TTL, e.g. 0.5-1.0s) keyed by inode/path. +2. Introduce targeted caches for hottest directories: + - `meshes/actors/character` + - `meshes/actors/character/facegenmorphs` + - `ShaderCache/*` + - `Seq` +3. Add optional “performance mode” config flag: + - less verbose logging + - larger TTLs + - aggressive directory blob reuse +4. Track `release`/`releasedir` deltas in logs to verify handle churn balance. +5. Validate `ulimit -n` at launch and warn if too low. + +## Useful Log Fields for Future Triage +- `ops lookup=... getattr=... readdir=... open=... read=... ioctl=... open_handles=...` +- `hot readdir: ...` +- `hot getattr: ...` +- `hot lookup_hit/lookup_miss: ...` + +## Bottom Line +The root misunderstanding was “fast VFS build should imply fast startup.” +In practice, startup was dominated by post-build metadata probing. +After multiple fixes, the system reached user-acceptable startup (`~2m40s`) with much better stability and no ioctl storm. |
