Skip to content

perf(inode): optimize lookupCount struct layout to 8-byte scalar - #5000

Draft
kislaykishore wants to merge 1 commit into
masterfrom
pr/opt-inode-lookup-count
Draft

perf(inode): optimize lookupCount struct layout to 8-byte scalar#5000
kislaykishore wants to merge 1 commit into
masterfrom
pr/opt-inode-lookup-count

Conversation

@kislaykishore

Copy link
Copy Markdown
Collaborator

Description

This PR optimizes the lookupCount helper in internal/fs/inode by converting it from a 24-byte composite struct ({id fuseops.InodeID, count uint64, destroyed bool}) into an 8-byte scalar int64 (where -1 denotes the destroyed state). Inode IDs are passed dynamically to Inc(id) and Dec(id, n) for panic formatting instead of being retained inside each inode instance.

Memory Savings & Go Heap Allocator Class Boundary

  • Crucially, FileInode shrinks from 304 B to 288 B, crossing a Go runtime heap allocator size class boundary (320 B -> 288 B).
  • Saves 32 bytes (-10.0%) of real heap memory per file inode, yielding 320.0 MB direct heap RSS savings per 10M file inodes.
  • Shrinks dirInode, SymlinkInode, and baseDirInode by 16 bytes each.
Struct / Type Baseline Size Optimized Size Struct Delta Go Heap Class Heap Allocation Delta RAM Saved (per 1M files) RAM Saved (per 10M files)
lookupCount 24 B 8 B -16 B (-66.7%) Stack/Internal
FileInode 304 B 288 B -16 B (-5.3%) 320 B -> 288 B -32 B (-10.0%) 32.0 MB 320.0 MB
dirInode 240 B 224 B -16 B (-6.7%) 240 B -> 224 B -16 B (-6.7%) 16.0 MB 160.0 MB
SymlinkInode 152 B 136 B -16 B (-10.5%) 160 B -> 144 B -16 B (-10.0%) 16.0 MB 160.0 MB
baseDirInode 152 B 136 B -16 B (-10.5%) 160 B -> 144 B -16 B (-10.0%) 16.0 MB 160.0 MB

Benchmark Performance

Measured via benchstat (n=10, p < 0.05):

Benchmark Target Master Baseline Optimized Branch CPU Latency Delta Baseline Memory Branch Memory Allocation Delta
LookupCount_Inc 2.161 ns/op 1.881 ns/op -12.93% (p=0.000) 0 B/op, 0 alloc 0 B/op, 0 alloc 0.0%
LookupCount_Dec 2.340 ns/op 2.285 ns/op -2.35% (p=0.043) 0 B/op, 0 alloc 0 B/op, 0 alloc 0.0%
Alloc_FileInode 130.9 ns/op 129.3 ns/op ~0.0% (parity) 320 B/op, 1 alloc 288 B/op, 1 alloc -10.0% B/op

Link to the issue in case of a bug fix.

N/A

Testing details

  1. Manual - Executed make build and verified formatting/linter with 0 issues.
  2. Unit tests - Executed unit tests: go test -v -race ./internal/fs/inode/....
  3. Integration tests - N/A

Any backward incompatible change? If so, please explain.

N/A

## Summary
This commit refactors `lookupCount` from a 24-byte composite struct (`{id, count, destroyed}`) into an 8-byte scalar `int64` (using `-1` as the sentinel for destroyed state). Inode IDs are passed dynamically on `Inc(id)` and `Dec(id, n)` for error formatting instead of being retained in the struct.

## Struct Sizing & Go Heap Class Reduction
Reducing `lookupCount` from 24 B to 8 B shrinks all inode types. Crucially, `FileInode` shrinks from 304 B to 288 B, crossing a Go runtime heap allocator size class boundary (320 B -> 288 B) and saving **32 bytes (-10.0%) of real heap memory per file inode**.

| Struct / Type | Baseline Size | Optimized Size | Struct Delta | Go Heap Class | Heap Allocation Delta | RAM Saved (per 1M files) | RAM Saved (per 10M files) |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| `lookupCount` | 24 B | **8 B** | -16 B (-66.7%) | Stack/Internal | — | — | — |
| `FileInode` | 304 B | **288 B** | -16 B (-5.3%) | 320 B -> **288 B** | **-32 B (-10.0%)** | **32.0 MB** | **320.0 MB** |
| `dirInode` | 240 B | **224 B** | -16 B (-6.7%) | 240 B -> **224 B** | **-16 B (-6.7%)** | **16.0 MB** | **160.0 MB** |
| `SymlinkInode` | 152 B | **136 B** | -16 B (-10.5%) | 160 B -> **144 B** | **-16 B (-10.0%)** | **16.0 MB** | **160.0 MB** |
| `baseDirInode` | 152 B | **136 B** | -16 B (-10.5%) | 160 B -> **144 B** | **-16 B (-10.0%)** | **16.0 MB** | **160.0 MB** |

## Benchmark Performance
Measured via `benchstat` (n=10, p < 0.05):

| Benchmark Target | Master Baseline | Optimized Branch | CPU Latency Delta | Baseline Memory | Branch Memory | Allocation Delta |
|---|:---:|:---:|:---:|:---:|:---:|:---:|
| `LookupCount_Inc` | 2.161 ns/op | **1.881 ns/op** | **-12.93% (p=0.000)** | 0 B/op, 0 alloc | 0 B/op, 0 alloc | 0.0% |
| `LookupCount_Dec` | 2.340 ns/op | **2.285 ns/op** | **-2.35% (p=0.043)** | 0 B/op, 0 alloc | 0 B/op, 0 alloc | 0.0% |
| `Alloc_FileInode` | 130.9 ns/op | 129.3 ns/op | ~0.0% (parity) | 320 B/op, 1 alloc | **288 B/op, 1 alloc** | **-10.0% B/op** |

## Verification
- Unit tests: `go test -v -race ./internal/fs/inode/...`
- Benchmarks: `go test -bench=BenchmarkLookupCount -benchmem ./internal/fs/inode/...`

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the lookupCount helper in internal/fs/inode/lookup_count.go from a struct to a custom int64 type, representing a destroyed state with a value of -1. This change simplifies the type and removes the need for an explicit initialization step, instead requiring the inode ID to be passed directly to the Inc and Dec methods. Corresponding updates were made across various inode implementations (base_dir.go, dir.go, file.go, and symlink.go) to pass the ID during increment/decrement operations and to explicitly call Destroy(). Additionally, a new test suite lookup_count_test.go was introduced to verify the behavior of the refactored lookupCount type. There are no review comments to address, and we have no further feedback to provide.

@codecov

codecov Bot commented Aug 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 83.86%. Comparing base (0673d19) to head (b5415c4).

Files with missing lines Patch % Lines
internal/fs/inode/base_dir.go 66.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5000      +/-   ##
==========================================
+ Coverage   83.81%   83.86%   +0.04%     
==========================================
  Files         174      174              
  Lines       21376    21376              
==========================================
+ Hits        17916    17926      +10     
+ Misses       2779     2773       -6     
+ Partials      681      677       -4     
Flag Coverage Δ
unittests 83.86% <96.00%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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