Skip to content

perf(fuse): eliminate per-read goroutine churn - #463

Merged
javi11 merged 3 commits into
mainfrom
perf/reduce-fuse-cpu-overhead
Apr 2, 2026
Merged

perf(fuse): eliminate per-read goroutine churn #463
javi11 merged 3 commits into
mainfrom
perf/reduce-fuse-cpu-overhead

Conversation

@javi11

@javi11 javi11 commented Apr 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Hanwen handle (Linux): Replace per-call readWithContext/seekWithContext goroutines with a single persistent IO worker goroutine per Handle. Previously every FUSE read spawned a goroutine + buffered channel (~24+/sec at 25 Mbps); now one worker per open file handles all IO via a request channel with pooled result channels.

Test plan

  • go test -race ./internal/nzbfilesystem/segcache/... passes
  • GOOS=linux go vet ./internal/fuse/backend/hanwen/... passes (tests require native Linux with CGO)
  • Stream a file via FUSE mount and verify CPU usage is lower than before, especially on repeated reads of the same content
  • Verify third read of same file is faster than second (hot cache serving from memory)
  • Set hot_cache_max_size_mb: 0 in config and verify it falls back to 256 MiB default

🤖 Generated with Claude Code

javi11 and others added 3 commits April 2, 2026 16:45
Two independent improvements to reduce CPU overhead when streaming via FUSE:

**1. Single IO worker per handle (Linux/hanwen)**
Replace `readWithContext`/`seekWithContext` which spawned a new goroutine
and buffered channel on every FUSE read call. A persistent background worker
goroutine per Handle now serializes all IO, with requests dispatched via a
buffered channel and results returned through a `sync.Pool`-backed channel.
Eliminates ~24+ goroutine+channel allocations per second at 25 Mbps.

**2. In-memory LRU hot cache for segment cache**
`SegmentCache.Get()` previously called `os.ReadFile()` on every cache hit,
allocating ~750 KB per segment read. A new LRU hot cache (default 256 MiB,
~341 segments) serves recently-accessed segments directly from memory,
avoiding repeated disk I/O and GC pressure from large heap allocations.
Configurable via `segment_cache.hot_cache_max_size_mb` in config.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
defer runs after the function returns, which is after AssertExpectations
checks that Close() was called. Fixed by calling Release explicitly
before AssertExpectations in each MockFile-based test; the deferred
call remains as a panic-safety net (Release is idempotent).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@javi11 javi11 changed the title perf(fuse): eliminate per-read goroutine churn and add segment hot cache perf(fuse): eliminate per-read goroutine churn Apr 2, 2026
@javi11
javi11 merged commit 8aa49a8 into main Apr 2, 2026
2 checks passed
yoshitaka420 pushed a commit to yoshitaka420/altmount that referenced this pull request Jun 1, 2026
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

1 participant