- 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>
239 lines
5.9 KiB
Go
239 lines
5.9 KiB
Go
// 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)
|
||
}
|
||
}
|
||
}
|