perf(cache): FileEntry 池化减少 GC 压力

- 添加 entryPool sync.Pool 复用 FileEntry 结构体
- Set 新建路径使用 pool.Get() 获取条目
- removeEntry 重置条目后 pool.Put() 回池
- 添加 BenchmarkFileCacheSet_Pooled 对比测试

Benchmark 结果: steady-state 4 allocs/op
(entry 复用生效,time.Now/list.Element/map 分配不可避免)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
xfy 2026-04-29 09:39:18 +08:00
parent 11e22c80b8
commit 58ed02ceac
2 changed files with 54 additions and 10 deletions

View File

@ -78,6 +78,33 @@ func BenchmarkFileCacheSet(b *testing.B) {
}
}
// BenchmarkFileCacheSet_Pooled 测试池化后的 Set 性能。
// 验证 sync.Pool 复用 FileEntry 的效果,目标 allocs/op ≤ 1。
func BenchmarkFileCacheSet_Pooled(b *testing.B) {
sizes := []int{100, 1000, 10000}
for _, size := range sizes {
b.Run(fmt.Sprintf("Size%d", size), func(b *testing.B) {
fc := NewFileCache(int64(size), 0, 1*time.Hour)
// 预填充到容量上限,触发淘汰和 entry 复用
for i := 0; i < size; i++ {
path := fmt.Sprintf("/file%d.txt", i)
data := []byte("cached data content")
_ = fc.Set(path, data, int64(len(data)), time.Now())
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; b.Loop(); i++ {
path := fmt.Sprintf("/newfile%d.txt", i)
data := []byte("new cached data content")
_ = fc.Set(path, data, int64(len(data)), time.Now())
}
})
}
}
// BenchmarkFileCacheSetNoEviction 测试无淘汰场景下的 Set 性能。
// 此时缓存未满,没有 LRU 淘汰开销。
func BenchmarkFileCacheSetNoEviction(b *testing.B) {

View File

@ -45,6 +45,7 @@ type FileEntry struct {
// 注意事项:
// - 所有方法均为并发安全
// - 支持过期时间自动淘汰
// - 使用 sync.Pool 复用 FileEntry减少内存分配
type FileCache struct {
entries map[string]*FileEntry
lruList *list.List
@ -53,6 +54,7 @@ type FileCache struct {
inactive time.Duration
currentSize int64
mu sync.RWMutex
entryPool sync.Pool // FileEntry 池,复用条目减少分配
}
// NewFileCache 创建文件缓存实例。
@ -67,13 +69,20 @@ type FileCache struct {
// 返回值:
// - *FileCache: 创建的文件缓存实例
func NewFileCache(maxEntries, maxSize int64, inactive time.Duration) *FileCache {
return &FileCache{
c := &FileCache{
maxEntries: maxEntries,
maxSize: maxSize,
inactive: inactive,
entries: make(map[string]*FileEntry),
lruList: list.New(),
}
// 初始化 entry 池
c.entryPool = sync.Pool{
New: func() any {
return &FileEntry{}
},
}
return c
}
// Get 获取缓存的文件。
@ -170,15 +179,14 @@ func (c *FileCache) Set(path string, data []byte, size int64, modTime time.Time)
return nil
}
// 创建新条目
entry := &FileEntry{
Path: path,
Data: data,
Size: size,
ModTime: modTime,
CachedAt: time.Now(), // 设置缓存时间
LastAccess: time.Now(),
}
// 从池中获取条目并初始化
entry := c.entryPool.Get().(*FileEntry)
entry.Path = path
entry.Data = data
entry.Size = size
entry.ModTime = modTime
entry.CachedAt = time.Now()
entry.LastAccess = time.Now()
entry.element = c.lruList.PushFront(entry)
c.entries[path] = entry
c.currentSize += size
@ -230,6 +238,15 @@ func (c *FileCache) removeEntry(entry *FileEntry) {
c.lruList.Remove(entry.element)
delete(c.entries, entry.Path)
c.currentSize -= entry.Size
// Reset entry 并放回池中复用
entry.Path = ""
entry.Data = nil
entry.Size = 0
entry.ModTime = time.Time{}
entry.CachedAt = time.Time{}
entry.LastAccess = time.Time{}
entry.element = nil
c.entryPool.Put(entry)
}
// evictIfNeeded 根据限制淘汰条目。