[performance] add caching of status fave, boost of, in reply to ID lists (#2060)

This commit is contained in:
kim
2023-08-04 12:28:33 +01:00
committed by GitHub
parent 00adf18c24
commit 9a291dea84
27 changed files with 610 additions and 406 deletions

View File

@@ -11,28 +11,7 @@ import (
"codeberg.org/gruf/go-errors/v2"
)
type result struct {
// Result primary key
PKey int64
// keys accessible under
Keys cacheKeys
// cached value
Value any
// cached error
Error error
}
// getResultValue is a safe way of casting and fetching result value.
func getResultValue[T any](res *result) T {
v, ok := res.Value.(T)
if !ok {
fmt.Fprintf(os.Stderr, "!! BUG: unexpected value type in result: %T\n", res.Value)
}
return v
}
var ErrUnsupportedZero = errors.New("")
// Lookup represents a struct object lookup method in the cache.
type Lookup struct {
@@ -255,13 +234,15 @@ func (c *Cache[T]) Load(lookup string, load func() (T, error), keyParts ...any)
evict = c.store(res)
}
// Catch and return error
if res.Error != nil {
return zero, res.Error
// Catch and return cached error
if err := res.Error; err != nil {
return zero, err
}
// Return a copy of value from cache
return c.copy(getResultValue[T](res)), nil
// Copy value from cached result.
v := c.copy(getResultValue[T](res))
return v, nil
}
// Store will call the given store function, and on success store the value in the cache as a positive result.
@@ -332,11 +313,13 @@ func (c *Cache[T]) Has(lookup string, keyParts ...any) bool {
}
}
// Check for result AND non-error result.
ok := (res != nil && res.Error == nil)
// Done with lock
c.cache.Unlock()
// Check for result AND non-error result.
return (res != nil && res.Error == nil)
return ok
}
// Invalidate will invalidate any result from the cache found under given lookup and key parts.
@@ -407,13 +390,18 @@ func (c *Cache[T]) store(res *result) (evict func()) {
key.info.pkeys[key.key] = pkeys
}
// Acquire new cache entry.
entry := simple.GetEntry()
entry.Key = res.PKey
entry.Value = res
evictFn := func(_ int64, entry *simple.Entry) {
// on evict during set, store evicted result.
toEvict = append(toEvict, entry.Value.(*result))
}
// Store main entry under primary key, catch evicted.
c.cache.Cache.SetWithHook(res.PKey, &simple.Entry{
Key: res.PKey,
Value: res,
}, func(_ int64, item *simple.Entry) {
toEvict = append(toEvict, item.Value.(*result))
})
c.cache.Cache.SetWithHook(res.PKey, entry, evictFn)
if len(toEvict) == 0 {
// none evicted.
@@ -421,9 +409,35 @@ func (c *Cache[T]) store(res *result) (evict func()) {
}
return func() {
for _, res := range toEvict {
for i := range toEvict {
// Rescope result.
res := toEvict[i]
// Call evict hook on each entry.
c.cache.Evict(res.PKey, res)
}
}
}
type result struct {
// Result primary key
PKey int64
// keys accessible under
Keys cacheKeys
// cached value
Value any
// cached error
Error error
}
// getResultValue is a safe way of casting and fetching result value.
func getResultValue[T any](res *result) T {
v, ok := res.Value.(T)
if !ok {
fmt.Fprintf(os.Stderr, "!! BUG: unexpected value type in result: %T\n", res.Value)
}
return v
}

View File

@@ -47,27 +47,32 @@ func (sk structKeys) generate(a any) []cacheKey {
buf := getBuf()
defer putBuf(buf)
outer:
for i := range sk {
// Reset buffer
buf.B = buf.B[:0]
buf.Reset()
// Append each field value to buffer.
for _, field := range sk[i].fields {
fv := v.Field(field.index)
fi := fv.Interface()
buf.B = field.mangle(buf.B, fi)
// Mangle this key part into buffer.
ok := field.manglePart(buf, fi)
if !ok {
// don't generate keys
// for zero value parts.
continue outer
}
// Append part separator.
buf.B = append(buf.B, '.')
}
// Drop last '.'
buf.Truncate(1)
// Don't generate keys for zero values
if allowZero := sk[i].zero == ""; // nocollapse
!allowZero && buf.String() == sk[i].zero {
continue
}
// Append new cached key to slice
keys = append(keys, cacheKey{
info: &sk[i],
@@ -114,14 +119,6 @@ type structKey struct {
// period ('.') separated struct field names.
name string
// zero is the possible zero value for this key.
// if set, this will _always_ be non-empty, as
// the mangled cache key will never be empty.
//
// i.e. zero = "" --> allow zero value keys
// zero != "" --> don't allow zero value keys
zero string
// unique determines whether this structKey supports
// multiple or just the singular unique result.
unique bool
@@ -135,47 +132,10 @@ type structKey struct {
pkeys map[string][]int64
}
type structField struct {
// index is the reflect index of this struct field.
index int
// mangle is the mangler function for
// serializing values of this struct field.
mangle mangler.Mangler
}
// genKey generates a cache key string for given key parts (i.e. serializes them using "go-mangler").
func (sk *structKey) genKey(parts []any) string {
// Check this expected no. key parts.
if len(parts) != len(sk.fields) {
panic(fmt.Sprintf("incorrect no. key parts provided: want=%d received=%d", len(parts), len(sk.fields)))
}
// Acquire byte buffer
buf := getBuf()
defer putBuf(buf)
buf.Reset()
// Encode each key part
for i, part := range parts {
buf.B = sk.fields[i].mangle(buf.B, part)
buf.B = append(buf.B, '.')
}
// Drop last '.'
buf.Truncate(1)
// Return string copy
return string(buf.B)
}
// newStructKey will generate a structKey{} information object for user-given lookup
// key information, and the receiving generic paramter's type information. Panics on error.
func newStructKey(lk Lookup, t reflect.Type) structKey {
var (
sk structKey
zeros []any
)
var sk structKey
// Set the lookup name
sk.name = lk.Name
@@ -183,9 +143,6 @@ func newStructKey(lk Lookup, t reflect.Type) structKey {
// Split dot-separated lookup to get
// the individual struct field names
names := strings.Split(lk.Name, ".")
if len(names) == 0 {
panic("no key fields specified")
}
// Allocate the mangler and field indices slice.
sk.fields = make([]structField, len(names))
@@ -213,16 +170,12 @@ func newStructKey(lk Lookup, t reflect.Type) structKey {
sk.fields[i].mangle = mangler.Get(ft.Type)
if !lk.AllowZero {
// Append the zero value interface
zeros = append(zeros, v.Interface())
// Append the mangled zero value interface
zero := sk.fields[i].mangle(nil, v.Interface())
sk.fields[i].zero = string(zero)
}
}
if len(zeros) > 0 {
// Generate zero value string
sk.zero = sk.genKey(zeros)
}
// Set unique lookup flag.
sk.unique = !lk.Multi
@@ -232,6 +185,68 @@ func newStructKey(lk Lookup, t reflect.Type) structKey {
return sk
}
// genKey generates a cache key string for given key parts (i.e. serializes them using "go-mangler").
func (sk *structKey) genKey(parts []any) string {
// Check this expected no. key parts.
if len(parts) != len(sk.fields) {
panic(fmt.Sprintf("incorrect no. key parts provided: want=%d received=%d", len(parts), len(sk.fields)))
}
// Acquire byte buffer
buf := getBuf()
defer putBuf(buf)
buf.Reset()
for i, part := range parts {
// Mangle this key part into buffer.
// specifically ignoring whether this
// is returning a zero value key part.
_ = sk.fields[i].manglePart(buf, part)
// Append part separator.
buf.B = append(buf.B, '.')
}
// Drop last '.'
buf.Truncate(1)
// Return string copy
return string(buf.B)
}
type structField struct {
// index is the reflect index of this struct field.
index int
// zero is the possible zero value for this
// key part. if set, this will _always_ be
// non-empty due to how the mangler works.
//
// i.e. zero = "" --> allow zero value keys
// zero != "" --> don't allow zero value keys
zero string
// mangle is the mangler function for
// serializing values of this struct field.
mangle mangler.Mangler
}
// manglePart ...
func (field *structField) manglePart(buf *byteutil.Buffer, part any) bool {
// Start of part bytes.
start := len(buf.B)
// Mangle this key part into buffer.
buf.B = field.mangle(buf.B, part)
// End of part bytes.
end := len(buf.B)
// Return whether this is zero value.
return (field.zero == "" ||
string(buf.B[start:end]) != field.zero)
}
// isExported checks whether function name is exported.
func isExported(fnName string) bool {
r, _ := utf8.DecodeRuneInString(fnName)
@@ -246,12 +261,12 @@ var bufPool = sync.Pool{
},
}
// getBuf ...
// getBuf acquires a byte buffer from memory pool.
func getBuf() *byteutil.Buffer {
return bufPool.Get().(*byteutil.Buffer)
}
// putBuf ...
// putBuf replaces a byte buffer back in memory pool.
func putBuf(buf *byteutil.Buffer) {
if buf.Cap() > int(^uint16(0)) {
return // drop large bufs

View File

@@ -102,7 +102,7 @@ func (c *Cache[K, V]) Add(key K, value V) bool {
}
// Alloc new entry.
new := getEntry()
new := GetEntry()
new.Key = key
new.Value = value
@@ -111,7 +111,7 @@ func (c *Cache[K, V]) Add(key K, value V) bool {
evcK = item.Key.(K)
evcV = item.Value.(V)
ev = true
putEntry(item)
PutEntry(item)
})
// Set hook func ptr.
@@ -161,7 +161,7 @@ func (c *Cache[K, V]) Set(key K, value V) {
item.Value = value
} else {
// Alloc new entry.
new := getEntry()
new := GetEntry()
new.Key = key
new.Value = value
@@ -170,7 +170,7 @@ func (c *Cache[K, V]) Set(key K, value V) {
evcK = item.Key.(K)
evcV = item.Value.(V)
ev = true
putEntry(item)
PutEntry(item)
})
}
@@ -311,7 +311,7 @@ func (c *Cache[K, V]) Invalidate(key K) (ok bool) {
_ = c.Cache.Delete(key)
// Free entry
putEntry(item)
PutEntry(item)
// Set hook func ptrs.
invalid = c.Invalid
@@ -367,7 +367,7 @@ func (c *Cache[K, V]) InvalidateAll(keys ...K) (ok bool) {
invalid(k, v)
// Free this entry.
putEntry(items[x])
PutEntry(items[x])
}
}
@@ -410,7 +410,7 @@ func (c *Cache[K, V]) Trim(perc float64) {
invalid(k, v)
// Free this entry.
putEntry(items[x])
PutEntry(items[x])
}
}
}
@@ -438,7 +438,7 @@ func (c *Cache[K, V]) locked(fn func()) {
func (c *Cache[K, V]) truncate(sz int, hook func(K, V)) []*Entry {
if hook == nil {
// No hook to execute, simply release all truncated entries.
c.Cache.Truncate(sz, func(_ K, item *Entry) { putEntry(item) })
c.Cache.Truncate(sz, func(_ K, item *Entry) { PutEntry(item) })
return nil
}

View File

@@ -6,8 +6,8 @@ import "sync"
// objects, regardless of cache type.
var entryPool sync.Pool
// getEntry fetches an Entry from pool, or allocates new.
func getEntry() *Entry {
// GetEntry fetches an Entry from pool, or allocates new.
func GetEntry() *Entry {
v := entryPool.Get()
if v == nil {
return new(Entry)
@@ -15,8 +15,8 @@ func getEntry() *Entry {
return v.(*Entry)
}
// putEntry replaces an Entry in the pool.
func putEntry(e *Entry) {
// PutEntry replaces an Entry in the pool.
func PutEntry(e *Entry) {
e.Key = nil
e.Value = nil
entryPool.Put(e)