package gocache import ( "fmt" "math/rand" "strconv" "strings" "testing" ) func BenchmarkMap_Get(b *testing.B) { m := make(map[string]any) for n := 0; n < b.N; n++ { _, _ = m[strconv.Itoa(n)] } b.ReportAllocs() } func BenchmarkMap_Set(b *testing.B) { values := map[string]string{ "small": "a", "medium": strings.Repeat("a", 1024), "large": strings.Repeat("a", 1024*100), } for name, value := range values { b.Run(fmt.Sprintf("%s value", name), func(b *testing.B) { m := make(map[string]any) for n := 0; n < b.N; n++ { m[strconv.Itoa(n)] = value } b.ReportAllocs() }) } } func BenchmarkCache_Get(b *testing.B) { evictionPolicies := []EvictionPolicy{FirstInFirstOut, LeastRecentlyUsed} for _, evictionPolicy := range evictionPolicies { cache := NewCache().WithMaxSize(NoMaxSize).WithMaxMemoryUsage(NoMaxMemoryUsage) b.Run(string(evictionPolicy), func(b *testing.B) { for n := 0; n < b.N; n++ { cache.Get(strconv.Itoa(n)) } b.ReportAllocs() }) } } func BenchmarkCache_Set(b *testing.B) { values := map[string]string{ "small": "a", "medium": strings.Repeat("a", 1024), "large": strings.Repeat("a", 1024*100), } evictionPolicies := []EvictionPolicy{FirstInFirstOut, LeastRecentlyUsed} for _, evictionPolicy := range evictionPolicies { for name, value := range values { b.Run(fmt.Sprintf("%s %s value", evictionPolicy, name), func(b *testing.B) { cache := NewCache().WithMaxSize(NoMaxSize).WithMaxMemoryUsage(NoMaxMemoryUsage).WithEvictionPolicy(evictionPolicy) for n := 0; n < b.N; n++ { cache.Set(strconv.Itoa(n), value) } b.ReportAllocs() }) } } } // BenchmarkCache_SetUsingMaxMemoryUsage does NOT test evictions, it tests the overhead of the extra work // automatically performed when using MaxMemoryUsage func BenchmarkCache_SetUsingMaxMemoryUsage(b *testing.B) { values := map[string]string{ "small": "a", "medium": strings.Repeat("a", 1024), "large": strings.Repeat("a", 1024*100), } for name, value := range values { b.Run(fmt.Sprintf("%s value", name), func(b *testing.B) { cache := NewCache().WithMaxSize(NoMaxSize).WithMaxMemoryUsage(999 * Gigabyte) for n := 0; n < b.N; n++ { cache.Set(strconv.Itoa(n), value) } b.ReportAllocs() }) } } func BenchmarkCache_SetWithMaxSize(b *testing.B) { values := map[string]string{ "small": "a", "medium": strings.Repeat("a", 1024), "large": strings.Repeat("a", 1024*100), } maxSizes := []int{100, 10000, 100000} for name, value := range values { for _, maxSize := range maxSizes { b.Run(fmt.Sprintf("%d %s value", maxSize, name), func(b *testing.B) { cache := NewCache().WithMaxSize(maxSize) for n := 0; n < b.N; n++ { cache.Set(strconv.Itoa(n), value) } b.ReportAllocs() }) } } } func BenchmarkCache_SetWithMaxSizeAndLRU(b *testing.B) { values := map[string]string{ "small": "a", "medium": strings.Repeat("a", 1024), "large": strings.Repeat("a", 1024*100), } maxSizes := []int{100, 10000, 100000} for name, value := range values { for _, maxSize := range maxSizes { b.Run(fmt.Sprintf("%d %s value", maxSize, name), func(b *testing.B) { cache := NewCache().WithMaxSize(maxSize).WithEvictionPolicy(LeastRecentlyUsed) for n := 0; n < b.N; n++ { cache.Set(strconv.Itoa(n), value) } b.ReportAllocs() }) } } } func BenchmarkCache_GetSetMultipleConcurrent(b *testing.B) { data := map[string]string{ "k1": "v1", "k2": "v2", "k3": "v3", "k4": "v4", "k5": "v5", "k6": "v6", "k7": "v7", "k8": "v8", } cache := NewCache().WithMaxSize(NoMaxSize) b.RunParallel(func(pb *testing.PB) { for pb.Next() { for k, v := range data { cache.Set(k, v) cache.Get(k) } } }) b.ReportAllocs() } func BenchmarkCache_GetSetConcurrentWithFrequentEviction(b *testing.B) { value := strings.Repeat("a", 256) evictionPolicies := []EvictionPolicy{FirstInFirstOut, LeastRecentlyUsed} for _, evictionPolicy := range evictionPolicies { b.Run(string(evictionPolicy), func(b *testing.B) { cache := NewCache().WithEvictionPolicy(LeastRecentlyUsed).WithMaxSize(3).WithMaxMemoryUsage(NoMaxMemoryUsage) b.RunParallel(func(pb *testing.PB) { for pb.Next() { k := strconv.Itoa(rand.Intn(15)) cache.Set(k, value) _, _ = cache.Get(k) } }) b.ReportAllocs() }) } } func BenchmarkCache_GetConcurrently(b *testing.B) { value := strings.Repeat("a", 256) for _, evictionPolicy := range []EvictionPolicy{FirstInFirstOut, LeastRecentlyUsed} { b.Run(string(evictionPolicy), func(b *testing.B) { cache := NewCache().WithMaxSize(100000) for i := 0; i < 100000; i++ { cache.Set(strconv.Itoa(i), value) } b.RunParallel(func(pb *testing.PB) { for pb.Next() { key := strconv.Itoa(rand.Intn(100000)) val, ok := cache.Get(key) if !ok { b.Errorf("key: %v; value: %v", key, val) } if val != value { b.Errorf("expected: %v; got: %v", val, value) } } }) b.ReportAllocs() }) } } // Note: The default value for Cache.forceNilInterfaceOnNilPointer is true func BenchmarkCache_WithForceNilInterfaceOnNilPointer(b *testing.B) { const ( Min = 10000 Max = 99999 ) type Struct struct { Value string } forceNilInterfaceOnNilPointerValues := []bool{true, false} values := []*Struct{nil, {Value: "value"}} for _, forceNilInterfaceOnNilPointer := range forceNilInterfaceOnNilPointerValues { for _, value := range values { name := fmt.Sprintf("%v", forceNilInterfaceOnNilPointer) if value == nil { name += " with nil struct pointer" } b.Run(name, func(b *testing.B) { cache := NewCache().WithMaxSize(NoMaxSize).WithMaxMemoryUsage(NoMaxMemoryUsage).WithForceNilInterfaceOnNilPointer(forceNilInterfaceOnNilPointer) for n := 0; n < b.N; n++ { cache.Set(strconv.Itoa(rand.Intn(Max-Min)+Min), value) } b.ReportAllocs() }) } } } func BenchmarkCache_WithForceNilInterfaceOnNilPointerWithConcurrency(b *testing.B) { const ( Min = 10000 Max = 99999 ) type Struct struct { Value string } forceNilInterfaceOnNilPointerValues := []bool{true, false} values := []*Struct{nil, {Value: "value"}} for _, forceNilInterfaceOnNilPointer := range forceNilInterfaceOnNilPointerValues { for _, value := range values { name := fmt.Sprintf("%v", forceNilInterfaceOnNilPointer) if value == nil { name += " with nil struct pointer" } b.Run(name, func(b *testing.B) { cache := NewCache().WithMaxSize(NoMaxSize).WithMaxMemoryUsage(NoMaxMemoryUsage).WithForceNilInterfaceOnNilPointer(forceNilInterfaceOnNilPointer) b.RunParallel(func(pb *testing.PB) { for pb.Next() { cache.Set(strconv.Itoa(rand.Intn(Max-Min)+Min), value) } }) b.ReportAllocs() }) } } }