Add a memoizedbloom
This commit is contained in:
5
go.mod
5
go.mod
@@ -3,3 +3,8 @@ module git.site.quack-lab.dev/dave/cyutils
|
||||
go 1.23.6
|
||||
|
||||
require golang.org/x/time v0.12.0
|
||||
|
||||
require (
|
||||
github.com/bits-and-blooms/bitset v1.24.2 // indirect
|
||||
github.com/bits-and-blooms/bloom/v3 v3.7.1 // indirect
|
||||
)
|
||||
|
||||
5
go.sum
5
go.sum
@@ -1,2 +1,7 @@
|
||||
github.com/bits-and-blooms/bitset v1.24.2 h1:M7/NzVbsytmtfHbumG+K2bremQPMJuqv1JD3vOaFxp0=
|
||||
github.com/bits-and-blooms/bitset v1.24.2/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/bits-and-blooms/bloom/v3 v3.7.1 h1:WXovk4TRKZttAMJfoQx6K2DM0zNIt8w+c67UqO+etV0=
|
||||
github.com/bits-and-blooms/bloom/v3 v3.7.1/go.mod h1:rZzYLLje2dfzXfAkJNxQQHsKurAyK55KUnL43Euk0hU=
|
||||
github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
|
||||
69
main.go
69
main.go
@@ -11,6 +11,7 @@ import (
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/bits-and-blooms/bloom/v3"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
@@ -259,3 +260,71 @@ func Memoized(fn interface{}) interface{} {
|
||||
return results
|
||||
}).Interface()
|
||||
}
|
||||
|
||||
// MemoizedBloom creates a memoized version of the provided function using a Bloom filter
|
||||
// to avoid caching "one hit wonders". Results are only cached on the second invocation
|
||||
// with the same arguments, reducing memory usage for functions with many unique calls.
|
||||
//
|
||||
// The function uses a Bloom filter to track which arguments have been seen:
|
||||
// - First call: Executes function, adds to Bloom filter, returns result (not cached)
|
||||
// - Second call: Executes function, caches result, returns result
|
||||
// - Subsequent calls: Returns cached result
|
||||
//
|
||||
// Parameters:
|
||||
// - fn: The function to memoize (must be a function type)
|
||||
// - capacity: Expected number of unique argument combinations (default: 10000)
|
||||
// - falsePositiveRate: Desired false positive rate for Bloom filter (default: 0.01)
|
||||
//
|
||||
// Returns a memoized version of the function with the same signature.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// func expensive(a string, b int) (string, error) {
|
||||
// fmt.Println("Computing...")
|
||||
// return fmt.Sprintf("Result: %s, %d", a, b), nil
|
||||
// }
|
||||
//
|
||||
// memoized := MemoizedBloom(expensive, 10000, 0.01).(func(string, int) (string, error))
|
||||
// result, _ := memoized("hello", 42) // Computes, adds to bloom filter
|
||||
// result, _ = memoized("hello", 42) // Computes again, caches result
|
||||
// result, _ = memoized("hello", 42) // Returns cached result
|
||||
//
|
||||
// Note: The cache key is generated using fmt.Sprintf("%v", args), which may not
|
||||
// uniquely identify all argument combinations. This function is thread-safe.
|
||||
func MemoizedBloom(fn interface{}, capacity uint, falsePositiveRate float64) interface{} {
|
||||
fnVal := reflect.ValueOf(fn)
|
||||
fnType := fnVal.Type()
|
||||
|
||||
if capacity == 0 {
|
||||
capacity = 10000
|
||||
}
|
||||
if falsePositiveRate == 0 {
|
||||
falsePositiveRate = 0.01
|
||||
}
|
||||
|
||||
bf := bloom.NewWithEstimates(capacity, falsePositiveRate)
|
||||
cache := make(map[string][]reflect.Value)
|
||||
var mu sync.Mutex
|
||||
|
||||
return reflect.MakeFunc(fnType, func(args []reflect.Value) []reflect.Value {
|
||||
key := fmt.Sprintf("%v", args)
|
||||
keyBytes := []byte(key)
|
||||
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
if cached, ok := cache[key]; ok {
|
||||
return cached
|
||||
}
|
||||
|
||||
results := fnVal.Call(args)
|
||||
|
||||
if bf.Test(keyBytes) {
|
||||
cache[key] = results
|
||||
} else {
|
||||
bf.Add(keyBytes)
|
||||
}
|
||||
|
||||
return results
|
||||
}).Interface()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user