lolly/internal/cache/cache_bench_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

294 lines
8.1 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 提供缓存模块的基准测试。
//
// 该文件测试缓存模块的性能,包括:
// - LRU Get/Set 操作性能
// - 并发读写性能
// - 不同缓存大小下的性能表现
//
// 作者xfy
package cache
import (
"fmt"
"hash/fnv"
"testing"
"time"
)
// hashKeyBench 计算字符串的 FNV-64a 哈希值,用于 benchmark。
func hashKeyBench(s string) uint64 {
h := fnv.New64a()
h.Write([]byte(s))
return h.Sum64()
}
// BenchmarkFileCacheGet 测试热点读取场景下的 Get 性能。
// 模拟缓存命中率高的场景,测试 LRU 链表的访问效率。
func BenchmarkFileCacheGet(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)
// 预填充缓存
for i := range size {
path := fmt.Sprintf("/file%d.txt", i)
data := []byte("cached data content")
_ = fc.Set(path, data, int64(len(data)), time.Now(), "text/plain")
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
// 热点读取:集中在前面 10% 的数据
path := fmt.Sprintf("/file%d.txt", i%(size/10+1))
fc.Get(path)
i++
}
})
})
}
}
// BenchmarkFileCacheSet 测试 Set 操作性能,包括 LRU 淘汰开销。
// 测试在缓存达到容量限制后的写入性能。
func BenchmarkFileCacheSet(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)
// 预填充到容量上限
for i := range size {
path := fmt.Sprintf("/file%d.txt", i)
data := []byte("cached data content")
_ = fc.Set(path, data, int64(len(data)), time.Now(), "text/plain")
}
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(), "text/plain")
}
})
}
}
// 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 := range size {
path := fmt.Sprintf("/file%d.txt", i)
data := []byte("cached data content")
_ = fc.Set(path, data, int64(len(data)), time.Now(), "text/plain")
}
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(), "text/plain")
}
})
}
}
// BenchmarkFileCacheSetNoEviction 测试无淘汰场景下的 Set 性能。
// 此时缓存未满,没有 LRU 淘汰开销。
func BenchmarkFileCacheSetNoEviction(b *testing.B) {
fc := NewFileCache(int64(b.N+1000), 0, 1*time.Hour)
b.ResetTimer()
for i := 0; b.Loop(); i++ {
path := fmt.Sprintf("/file%d.txt", i)
data := []byte("cached data content")
_ = fc.Set(path, data, int64(len(data)), time.Now(), "text/plain")
}
}
// BenchmarkFileCacheConcurrent 测试并发读写混合负载性能。
// 使用 90% Get / 10% Set 的混合负载模拟真实场景。
func BenchmarkFileCacheConcurrent(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)
// 预填充缓存
for i := range size {
path := fmt.Sprintf("/file%d.txt", i)
data := []byte("cached data content")
_ = fc.Set(path, data, int64(len(data)), time.Now(), "text/plain")
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
// 90% Get, 10% Set
if i%10 == 0 {
path := fmt.Sprintf("/newfile%d.txt", i)
data := []byte("updated data content")
_ = fc.Set(path, data, int64(len(data)), time.Now(), "text/plain")
} else {
path := fmt.Sprintf("/file%d.txt", i%size)
fc.Get(path)
}
i++
}
})
})
}
}
// BenchmarkFileCacheGetOnly 测试纯读场景下的性能。
// 模拟静态文件服务的缓存读取。
func BenchmarkFileCacheGetOnly(b *testing.B) {
fc := NewFileCache(1000, 0, 1*time.Hour)
// 预填充缓存
for i := range 1000 {
path := fmt.Sprintf("/static/file%d.css", i)
data := make([]byte, 1024) // 1KB 数据
_ = fc.Set(path, data, int64(len(data)), time.Now(), "text/plain")
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
path := fmt.Sprintf("/static/file%d.css", i%1000)
fc.Get(path)
i++
}
})
}
// BenchmarkFileCacheSizeEviction 测试基于内存大小的淘汰性能。
// 测试当缓存超过内存限制时的淘汰开销。
func BenchmarkFileCacheSizeEviction(b *testing.B) {
// 限制最大 1MB 内存
maxSize := int64(1024 * 1024)
fc := NewFileCache(0, maxSize, 1*time.Hour)
// 预填充到接近容量上限
data := make([]byte, 1024) // 1KB 每条
for i := range 1000 {
path := fmt.Sprintf("/file%d.txt", i)
_ = fc.Set(path, data, int64(len(data)), time.Now(), "text/plain")
}
b.ResetTimer()
for i := 0; b.Loop(); i++ {
path := fmt.Sprintf("/newfile%d.txt", i)
newData := make([]byte, 1024)
_ = fc.Set(path, newData, int64(len(newData)), time.Now(), "text/plain")
}
}
// BenchmarkFileCacheLRUTouch 测试 LRU 链表更新开销。
// 频繁访问同一批条目,观察 LRU 移动性能。
func BenchmarkFileCacheLRUTouch(b *testing.B) {
fc := NewFileCache(100, 0, 1*time.Hour)
// 预填充缓存
for i := range 100 {
path := fmt.Sprintf("/file%d.txt", i)
data := []byte("cached data")
_ = fc.Set(path, data, int64(len(data)), time.Now(), "text/plain")
}
b.ResetTimer()
for i := 0; b.Loop(); i++ {
// 按顺序访问,触发 LRU 链表更新
path := fmt.Sprintf("/file%d.txt", i%100)
fc.Get(path)
}
}
// BenchmarkProxyCacheGet 测试代理缓存 Get 性能。
func BenchmarkProxyCacheGet(b *testing.B) {
pc := NewProxyCache(nil, false, 0, 0, 0)
// 预填充缓存
for i := range 1000 {
origKey := fmt.Sprintf("key%d", i)
hashKey := hashKeyBench(origKey)
data := []byte("response body")
headers := map[string]string{"Content-Type": "application/json"}
pc.Set(hashKey, origKey, data, headers, 200, 10*time.Minute)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
origKey := fmt.Sprintf("key%d", i%1000)
hashKey := hashKeyBench(origKey)
pc.Get(hashKey, origKey)
i++
}
})
}
// BenchmarkProxyCacheSet 测试代理缓存 Set 性能。
func BenchmarkProxyCacheSet(b *testing.B) {
pc := NewProxyCache(nil, false, 0, 0, 0)
data := []byte("response body")
headers := map[string]string{"Content-Type": "application/json"}
b.ResetTimer()
for i := 0; b.Loop(); i++ {
origKey := fmt.Sprintf("key%d", i)
hashKey := hashKeyBench(origKey)
pc.Set(hashKey, origKey, data, headers, 200, 10*time.Minute)
}
}
// BenchmarkProxyCacheConcurrent 测试代理缓存并发混合负载。
// 使用 90% Get / 10% Set 的混合负载。
func BenchmarkProxyCacheConcurrent(b *testing.B) {
pc := NewProxyCache(nil, false, 0, 0, 0)
// 预填充缓存
for i := range 1000 {
origKey := fmt.Sprintf("key%d", i)
hashKey := hashKeyBench(origKey)
data := []byte("response body")
headers := map[string]string{"Content-Type": "application/json"}
pc.Set(hashKey, origKey, data, headers, 200, 10*time.Minute)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
if i%10 == 0 {
origKey := fmt.Sprintf("newkey%d", i)
hashKey := hashKeyBench(origKey)
data := []byte("new response body")
headers := map[string]string{"Content-Type": "application/json"}
pc.Set(hashKey, origKey, data, headers, 200, 10*time.Minute)
} else {
origKey := fmt.Sprintf("key%d", i%1000)
hashKey := hashKeyBench(origKey)
pc.Get(hashKey, origKey)
}
i++
}
})
}