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:
parent
11e22c80b8
commit
58ed02ceac
27
internal/cache/cache_bench_test.go
vendored
27
internal/cache/cache_bench_test.go
vendored
@ -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) {
|
||||
|
||||
37
internal/cache/file_cache.go
vendored
37
internal/cache/file_cache.go
vendored
@ -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 根据限制淘汰条目。
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user