mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
[performance] add caching of status fave, boost of, in reply to ID lists (#2060)
This commit is contained in:
86
vendor/codeberg.org/gruf/go-cache/v3/result/cache.go
generated
vendored
86
vendor/codeberg.org/gruf/go-cache/v3/result/cache.go
generated
vendored
@@ -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
|
||||
}
|
||||
|
147
vendor/codeberg.org/gruf/go-cache/v3/result/key.go
generated
vendored
147
vendor/codeberg.org/gruf/go-cache/v3/result/key.go
generated
vendored
@@ -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
|
||||
|
16
vendor/codeberg.org/gruf/go-cache/v3/simple/cache.go
generated
vendored
16
vendor/codeberg.org/gruf/go-cache/v3/simple/cache.go
generated
vendored
@@ -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
|
||||
}
|
||||
|
||||
|
8
vendor/codeberg.org/gruf/go-cache/v3/simple/pool.go
generated
vendored
8
vendor/codeberg.org/gruf/go-cache/v3/simple/pool.go
generated
vendored
@@ -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)
|
||||
|
Reference in New Issue
Block a user