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 性能。
|
// BenchmarkFileCacheSetNoEviction 测试无淘汰场景下的 Set 性能。
|
||||||
// 此时缓存未满,没有 LRU 淘汰开销。
|
// 此时缓存未满,没有 LRU 淘汰开销。
|
||||||
func BenchmarkFileCacheSetNoEviction(b *testing.B) {
|
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 {
|
type FileCache struct {
|
||||||
entries map[string]*FileEntry
|
entries map[string]*FileEntry
|
||||||
lruList *list.List
|
lruList *list.List
|
||||||
@ -53,6 +54,7 @@ type FileCache struct {
|
|||||||
inactive time.Duration
|
inactive time.Duration
|
||||||
currentSize int64
|
currentSize int64
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
entryPool sync.Pool // FileEntry 池,复用条目减少分配
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFileCache 创建文件缓存实例。
|
// NewFileCache 创建文件缓存实例。
|
||||||
@ -67,13 +69,20 @@ type FileCache struct {
|
|||||||
// 返回值:
|
// 返回值:
|
||||||
// - *FileCache: 创建的文件缓存实例
|
// - *FileCache: 创建的文件缓存实例
|
||||||
func NewFileCache(maxEntries, maxSize int64, inactive time.Duration) *FileCache {
|
func NewFileCache(maxEntries, maxSize int64, inactive time.Duration) *FileCache {
|
||||||
return &FileCache{
|
c := &FileCache{
|
||||||
maxEntries: maxEntries,
|
maxEntries: maxEntries,
|
||||||
maxSize: maxSize,
|
maxSize: maxSize,
|
||||||
inactive: inactive,
|
inactive: inactive,
|
||||||
entries: make(map[string]*FileEntry),
|
entries: make(map[string]*FileEntry),
|
||||||
lruList: list.New(),
|
lruList: list.New(),
|
||||||
}
|
}
|
||||||
|
// 初始化 entry 池
|
||||||
|
c.entryPool = sync.Pool{
|
||||||
|
New: func() any {
|
||||||
|
return &FileEntry{}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get 获取缓存的文件。
|
// Get 获取缓存的文件。
|
||||||
@ -170,15 +179,14 @@ func (c *FileCache) Set(path string, data []byte, size int64, modTime time.Time)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建新条目
|
// 从池中获取条目并初始化
|
||||||
entry := &FileEntry{
|
entry := c.entryPool.Get().(*FileEntry)
|
||||||
Path: path,
|
entry.Path = path
|
||||||
Data: data,
|
entry.Data = data
|
||||||
Size: size,
|
entry.Size = size
|
||||||
ModTime: modTime,
|
entry.ModTime = modTime
|
||||||
CachedAt: time.Now(), // 设置缓存时间
|
entry.CachedAt = time.Now()
|
||||||
LastAccess: time.Now(),
|
entry.LastAccess = time.Now()
|
||||||
}
|
|
||||||
entry.element = c.lruList.PushFront(entry)
|
entry.element = c.lruList.PushFront(entry)
|
||||||
c.entries[path] = entry
|
c.entries[path] = entry
|
||||||
c.currentSize += size
|
c.currentSize += size
|
||||||
@ -230,6 +238,15 @@ func (c *FileCache) removeEntry(entry *FileEntry) {
|
|||||||
c.lruList.Remove(entry.element)
|
c.lruList.Remove(entry.element)
|
||||||
delete(c.entries, entry.Path)
|
delete(c.entries, entry.Path)
|
||||||
c.currentSize -= entry.Size
|
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 根据限制淘汰条目。
|
// evictIfNeeded 根据限制淘汰条目。
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user