lolly/internal/cache/file_cache_allocation_test.go
xfy 3c96f12f74 feat(cache): store ContentType in FileEntry for cache hits
- Add ContentType field to FileEntry struct
- Update Set method signature to accept contentType parameter
- Use cached ContentType in static.go cache hit branches
- Update all test files to use new Set signature

This avoids redundant MIME type detection on cache hits,
reducing lock contention in mimeutil.DetectContentType.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 16:13:42 +08:00

239 lines
5.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package cache 提供文件缓存分配热点追踪测试。
//
// 该文件追踪 FileCache.Set 的分配来源,验证池化优化效果。
//
// 测试场景:
// - SetNew: 新建条目路径
// - SetUpdate: 更新已存在条目路径
// - SetEviction: 淘汰场景(缓存满载)
//
// 目标:池化后 ≤1 allocs/op
//
// 作者xfy
package cache
import (
"container/list"
"fmt"
"sync"
"testing"
"time"
)
// BenchmarkFileCacheSetAllocation_New 测试新建条目路径的分配。
//
// 热点分配来源:
// - entry := c.entryPool.Get() -> 池化后 0 allocs
// - c.lruList.PushFront(entry) -> list Element 分配
// - c.entries[path] = entry -> map 写入
func BenchmarkFileCacheSetAllocation_New(b *testing.B) {
fc := NewFileCache(10000, 0, 1*time.Hour)
data := []byte("test data content for benchmark")
size := int64(len(data))
b.ReportAllocs()
b.ResetTimer()
for i := 0; b.Loop(); i++ {
path := fmt.Sprintf("/new/file%d.txt", i)
fc.Set(path, data, size, time.Now(), "text/plain")
}
}
// BenchmarkFileCacheSetAllocation_Update 测试更新已存在条目路径的分配。
//
// 更新路径理论上 0 allocs不涉及新条目分配
func BenchmarkFileCacheSetAllocation_Update(b *testing.B) {
fc := NewFileCache(10000, 0, 1*time.Hour)
// 预填充缓存
data := []byte("test data content for benchmark")
size := int64(len(data))
for i := range 1000 {
path := fmt.Sprintf("/update/file%d.txt", i)
fc.Set(path, data, size, time.Now(), "text/plain")
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; b.Loop(); i++ {
// 循环更新已有条目
path := fmt.Sprintf("/update/file%d.txt", i%1000)
fc.Set(path, data, size, time.Now(), "text/plain")
}
}
// BenchmarkFileCacheSetAllocation_Eviction 测试淘汰场景的分配。
//
// 热点:
// - 淘汰时 removeEntry -> entryPool.Put(entry)
// - 新 Set 时 entryPool.Get() 复用
// - 理论上 LRU Element 分配仍是热点
func BenchmarkFileCacheSetAllocation_Eviction(b *testing.B) {
// 容量限制为 100强制触发淘汰
fc := NewFileCache(100, 0, 1*time.Hour)
// 预填充到容量上限
data := []byte("test data content for benchmark")
size := int64(len(data))
for i := range 100 {
path := fmt.Sprintf("/evict/file%d.txt", i)
fc.Set(path, data, size, time.Now(), "text/plain")
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; b.Loop(); i++ {
// 每个 Set 都触发淘汰
path := fmt.Sprintf("/evict/new%d.txt", i)
fc.Set(path, data, size, time.Now(), "text/plain")
}
}
// BenchmarkFileCacheSetAllocation_EvictionWithPool 测试淘汰+池化复用的分配。
//
// 验证 entryPool 在淘汰场景的复用效果。
func BenchmarkFileCacheSetAllocation_EvictionWithPool(b *testing.B) {
fc := NewFileCache(100, 0, 1*time.Hour)
data := []byte("test data content for benchmark")
size := int64(len(data))
// 预填充
for i := range 100 {
path := fmt.Sprintf("/pool/file%d.txt", i)
fc.Set(path, data, size, time.Now(), "text/plain")
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; b.Loop(); i++ {
path := fmt.Sprintf("/pool/new%d.txt", i)
fc.Set(path, data, size, time.Now(), "text/plain")
}
}
// BenchmarkFileCacheSetAllocation_MemoryLimit 测试内存限制淘汰的分配。
//
// 热点evictIfNeeded 需遍历 LRU 链表淘汰多个条目。
func BenchmarkFileCacheSetAllocation_MemoryLimit(b *testing.B) {
// 限制 1MB每个条目 1KB约 1000 条目
fc := NewFileCache(0, 1024*1024, 1*time.Hour)
data := make([]byte, 1024) // 1KB 每条目
size := int64(len(data))
// 预填充到接近上限
for i := range 900 {
path := fmt.Sprintf("/mem/file%d.txt", i)
fc.Set(path, data, size, time.Now(), "text/plain")
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; b.Loop(); i++ {
path := fmt.Sprintf("/mem/new%d.txt", i)
fc.Set(path, data, size, time.Now(), "text/plain")
}
}
// BenchmarkFileCacheSetAllocation_Concurrent 测试并发 Set 的分配。
//
// 并发场景下锁竞争可能影响池化效果。
func BenchmarkFileCacheSetAllocation_Concurrent(b *testing.B) {
fc := NewFileCache(10000, 0, 1*time.Hour)
data := []byte("concurrent test data")
size := int64(len(data))
b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
path := fmt.Sprintf("/conc/file%d.txt", i)
fc.Set(path, data, size, time.Now(), "text/plain")
i++
}
})
}
// BenchmarkFileCacheSetAllocation_ConcurrentEviction 测试并发淘汰场景。
func BenchmarkFileCacheSetAllocation_ConcurrentEviction(b *testing.B) {
fc := NewFileCache(100, 0, 1*time.Hour)
data := []byte("eviction concurrent")
size := int64(len(data))
// 预填充
for i := range 100 {
path := fmt.Sprintf("/concevict/file%d.txt", i)
fc.Set(path, data, size, time.Now(), "text/plain")
}
b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
path := fmt.Sprintf("/concevict/new%d.txt", i)
fc.Set(path, data, size, time.Now(), "text/plain")
i++
}
})
}
// BenchmarkFileCacheEntryPool_GetPut 测试 entryPool 直接操作。
//
// 验证 sync.Pool 的分配效果(应 0 allocs
func BenchmarkFileCacheEntryPool_GetPut(b *testing.B) {
pool := &sync.Pool{
New: func() any {
return &FileEntry{}
},
}
// 预填充池
for range 100 {
pool.Put(&FileEntry{})
}
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
entry := pool.Get().(*FileEntry)
// 模拟使用
entry.Path = "/test"
entry.Data = nil
pool.Put(entry)
}
}
// BenchmarkFileCacheLRUList_PushFront 测试 LRU 链表操作分配。
//
// 热点list.PushFront 分配 Element约 1 allocs
func BenchmarkFileCacheLRUList_PushFront(b *testing.B) {
lruList := list.New()
b.ReportAllocs()
b.ResetTimer()
for i := 0; b.Loop(); i++ {
entry := &FileEntry{Path: fmt.Sprintf("/lru%d", i)}
lruList.PushFront(entry)
// 模拟淘汰移除
if lruList.Len() > 100 {
old := lruList.Back()
lruList.Remove(old)
}
}
}