go-twig/attr_cache_benchmark_test.go
semihalev f1add2d820 Implement efficient LRU eviction strategy for attribute cache
- Added tracking of last access time and access count to cache entries
- Implemented eviction policy to remove least recently/frequently used entries
- Cache now removes 10% of entries when the cache is full, prioritizing by usage
- Added benchmarks and tests to verify eviction strategy
- Fixed the previously ineffective eviction strategy
- Improved cache efficiency for applications with many diverse object types

🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-03-11 14:20:47 +03:00

154 lines
No EOL
3.6 KiB
Go

package twig
import (
"fmt"
"reflect"
"testing"
)
// Struct for testing attribute cache
type testType struct {
Field1 string
Field2 int
Field3 bool
}
func (t *testType) Method1() string {
return t.Field1
}
func (t testType) Method2() int {
return t.Field2
}
// Create many distinct types to stress the attribute cache
type dynamicType struct {
name string
fields map[string]interface{}
}
// Custom interface to showcase reflection
type displayable interface {
Display() string
}
func (d dynamicType) Display() string {
return fmt.Sprintf("Type: %s", d.name)
}
// Benchmark the attribute cache with a small number of types
func BenchmarkAttributeCache_FewTypes(b *testing.B) {
// Reset the attribute cache
attributeCache.Lock()
attributeCache.m = make(map[attributeCacheKey]attributeCacheEntry)
attributeCache.currSize = 0
attributeCache.Unlock()
// Create a render context
ctx := NewRenderContext(nil, nil, nil)
defer ctx.Release()
obj := &testType{
Field1: "test",
Field2: 123,
Field3: true,
}
// Run the benchmark
b.ResetTimer()
for i := 0; i < b.N; i++ {
// Access different fields and methods
_, _ = ctx.getAttribute(obj, "Field1")
_, _ = ctx.getAttribute(obj, "Field2")
_, _ = ctx.getAttribute(obj, "Field3")
_, _ = ctx.getAttribute(obj, "Method1")
_, _ = ctx.getAttribute(obj, "Method2")
}
}
// Benchmark the attribute cache with many different types
func BenchmarkAttributeCache_ManyTypes(b *testing.B) {
// Reset the attribute cache
attributeCache.Lock()
attributeCache.m = make(map[attributeCacheKey]attributeCacheEntry)
attributeCache.currSize = 0
attributeCache.Unlock()
// Create a render context
ctx := NewRenderContext(nil, nil, nil)
defer ctx.Release()
// Create 2000 different types (more than the cache limit)
types := make([]interface{}, 2000)
for i := 0; i < 2000; i++ {
types[i] = dynamicType{
name: fmt.Sprintf("Type%d", i),
fields: map[string]interface{}{
"field1": fmt.Sprintf("value%d", i),
"field2": i,
},
}
}
// Run the benchmark
b.ResetTimer()
for i := 0; i < b.N; i++ {
// Access attributes across different types
typeIdx := i % 2000
_, _ = ctx.getAttribute(types[typeIdx], "name")
_, _ = ctx.getAttribute(types[typeIdx], "fields")
}
}
// Verify that the attribute cache properly performs LRU eviction
func TestAttributeCacheLRUEviction(t *testing.T) {
// Reset the attribute cache
attributeCache.Lock()
attributeCache.m = make(map[attributeCacheKey]attributeCacheEntry)
attributeCache.currSize = 0
attributeCache.maxSize = 10 // Small size for testing
attributeCache.Unlock()
// Create a render context
ctx := NewRenderContext(nil, nil, nil)
defer ctx.Release()
// Create 20 different types (more than the cache size)
types := make([]interface{}, 20)
for i := 0; i < 20; i++ {
types[i] = dynamicType{
name: fmt.Sprintf("Type%d", i),
fields: map[string]interface{}{
"field1": fmt.Sprintf("value%d", i),
},
}
}
// First access all types once
for i := 0; i < 20; i++ {
_, _ = ctx.getAttribute(types[i], "name")
}
// Now access the last 5 types more frequently
for i := 0; i < 100; i++ {
typeIdx := 15 + (i % 5) // Types 15-19
_, _ = ctx.getAttribute(types[typeIdx], "name")
}
// Check which types are in the cache
attributeCache.RLock()
defer attributeCache.RUnlock()
// The most recently/frequently used types should be in the cache
for i := 15; i < 20; i++ {
typeKey := attributeCacheKey{
typ: reflect.TypeOf(types[i]),
attr: "name",
}
_, found := attributeCache.m[typeKey]
if !found {
t.Errorf("Expected type %d to be in cache, but it wasn't", i)
}
}
}