mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
[performance] cache library performance enhancements (updates go-structr => v0.2.0) (#2575)
* update go-structr => v0.2.0 * update readme * whoops, fix the link
This commit is contained in:
70
vendor/codeberg.org/gruf/go-structr/README.md
generated
vendored
70
vendor/codeberg.org/gruf/go-structr/README.md
generated
vendored
@@ -2,4 +2,74 @@
|
||||
|
||||
A performant struct caching library with automated indexing by arbitrary combinations of fields, including support for negative results (errors!). An example use case is in database lookups.
|
||||
|
||||
Some example code of how you can use `go-structr` in your application:
|
||||
```golang
|
||||
type Cached struct {
|
||||
Username string
|
||||
Domain string
|
||||
URL string
|
||||
CountryCode int
|
||||
}
|
||||
|
||||
var c structr.Cache[*Cached]
|
||||
|
||||
c.Init(structr.Config[*Cached]{
|
||||
|
||||
// Fields this cached struct type
|
||||
// will be indexed and stored under.
|
||||
Indices: []structr.IndexConfig{
|
||||
{Fields: "Username,Domain", AllowZero: true},
|
||||
{Fields: "URL"},
|
||||
{Fields: "CountryCode", Multiple: true},
|
||||
},
|
||||
|
||||
// Maximum LRU cache size before
|
||||
// new entries cause evictions.
|
||||
MaxSize: 1000,
|
||||
|
||||
// User provided value copy function to
|
||||
// reduce need for reflection + ensure
|
||||
// concurrency safety for returned values.
|
||||
CopyValue: func(c *Cached) *Cached {
|
||||
c2 := new(Cached)
|
||||
*c2 = *c
|
||||
return c2
|
||||
},
|
||||
|
||||
// User defined invalidation hook.
|
||||
Invalidate: func(c *Cached) {
|
||||
log.Println("invalidated:", c)
|
||||
},
|
||||
})
|
||||
|
||||
var url string
|
||||
|
||||
// Load value from cache, with callback function to hydrate
|
||||
// cache if value cannot be found under index name with key.
|
||||
// Negative (error) results are also cached, with user definable
|
||||
// errors to ignore from caching (e.g. context deadline errs).
|
||||
value, err := c.LoadOne("URL", func() (*Cached, error) {
|
||||
return dbType.SelectByURL(url)
|
||||
}, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Store value in cache, only if provided callback
|
||||
// function returns without error. Passes value through
|
||||
// invalidation hook regardless of error return value.
|
||||
//
|
||||
// On success value will be automatically added to and
|
||||
// accessible under all initially configured indices.
|
||||
if err := c.Store(value, func() error {
|
||||
return dbType.Insert(value)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Invalidate all cached results stored under
|
||||
// provided index name with give field value(s).
|
||||
c.Invalidate("CountryCode", 42)
|
||||
```
|
||||
|
||||
This is a core underpinning of [GoToSocial](https://github.com/superseriousbusiness/gotosocial)'s performance.
|
83
vendor/codeberg.org/gruf/go-structr/cache.go
generated
vendored
83
vendor/codeberg.org/gruf/go-structr/cache.go
generated
vendored
@@ -111,8 +111,8 @@ func (c *Cache[T]) Init(config Config[T]) {
|
||||
// provided config.
|
||||
c.mutex.Lock()
|
||||
c.indices = make([]Index[T], len(config.Indices))
|
||||
for i, config := range config.Indices {
|
||||
c.indices[i].init(config)
|
||||
for i, cfg := range config.Indices {
|
||||
c.indices[i].init(cfg, config.MaxSize)
|
||||
}
|
||||
c.ignore = config.IgnoreErr
|
||||
c.copy = config.CopyValue
|
||||
@@ -138,7 +138,7 @@ func (c *Cache[T]) GetOne(index string, keyParts ...any) (T, bool) {
|
||||
idx := c.Index(index)
|
||||
|
||||
// Generate index key from provided parts.
|
||||
key, ok := idx.keygen.FromParts(keyParts...)
|
||||
key, ok := idx.hasher.FromParts(keyParts...)
|
||||
if !ok {
|
||||
var zero T
|
||||
return zero, false
|
||||
@@ -149,7 +149,7 @@ func (c *Cache[T]) GetOne(index string, keyParts ...any) (T, bool) {
|
||||
}
|
||||
|
||||
// GetOneBy fetches value from cache stored under index, using precalculated index key.
|
||||
func (c *Cache[T]) GetOneBy(index *Index[T], key string) (T, bool) {
|
||||
func (c *Cache[T]) GetOneBy(index *Index[T], key uint64) (T, bool) {
|
||||
if index == nil {
|
||||
panic("no index given")
|
||||
} else if !index.unique {
|
||||
@@ -170,37 +170,33 @@ func (c *Cache[T]) Get(index string, keysParts ...[]any) []T {
|
||||
idx := c.Index(index)
|
||||
|
||||
// Preallocate expected keys slice length.
|
||||
keys := make([]string, 0, len(keysParts))
|
||||
keys := make([]uint64, 0, len(keysParts))
|
||||
|
||||
// Acquire buf.
|
||||
buf := getBuf()
|
||||
// Acquire hasher.
|
||||
h := getHasher()
|
||||
|
||||
for _, parts := range keysParts {
|
||||
// Reset buf.
|
||||
buf.Reset()
|
||||
h.Reset()
|
||||
|
||||
// Generate key from provided parts into buffer.
|
||||
if !idx.keygen.AppendFromParts(buf, parts...) {
|
||||
key, ok := idx.hasher.fromParts(h, parts...)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get string copy of
|
||||
// genarated idx key.
|
||||
key := string(buf.B)
|
||||
|
||||
// Append key to keys.
|
||||
// Append hash sum to keys.
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
// Done with buf.
|
||||
putBuf(buf)
|
||||
// Done with h.
|
||||
putHasher(h)
|
||||
|
||||
// Continue fetching values.
|
||||
return c.GetBy(idx, keys...)
|
||||
}
|
||||
|
||||
// GetBy fetches values from the cache stored under index, using precalculated index keys.
|
||||
func (c *Cache[T]) GetBy(index *Index[T], keys ...string) []T {
|
||||
func (c *Cache[T]) GetBy(index *Index[T], keys ...uint64) []T {
|
||||
if index == nil {
|
||||
panic("no index given")
|
||||
}
|
||||
@@ -265,7 +261,7 @@ func (c *Cache[T]) Put(values ...T) {
|
||||
|
||||
// Store all the passed values.
|
||||
for _, value := range values {
|
||||
c.store(nil, "", value, nil)
|
||||
c.store(nil, 0, value, nil)
|
||||
}
|
||||
|
||||
// Done with lock.
|
||||
@@ -288,7 +284,7 @@ func (c *Cache[T]) LoadOne(index string, load func() (T, error), keyParts ...any
|
||||
idx := c.Index(index)
|
||||
|
||||
// Generate cache from from provided parts.
|
||||
key, _ := idx.keygen.FromParts(keyParts...)
|
||||
key, _ := idx.hasher.FromParts(keyParts...)
|
||||
|
||||
// Continue loading this result.
|
||||
return c.LoadOneBy(idx, load, key)
|
||||
@@ -296,7 +292,7 @@ func (c *Cache[T]) LoadOne(index string, load func() (T, error), keyParts ...any
|
||||
|
||||
// LoadOneBy fetches one result from the cache stored under index, using precalculated index key.
|
||||
// In the case that no result is found, provided load callback will be used to hydrate the cache.
|
||||
func (c *Cache[T]) LoadOneBy(index *Index[T], load func() (T, error), key string) (T, error) {
|
||||
func (c *Cache[T]) LoadOneBy(index *Index[T], load func() (T, error), key uint64) (T, error) {
|
||||
if index == nil {
|
||||
panic("no index given")
|
||||
} else if !index.unique {
|
||||
@@ -421,26 +417,21 @@ func (c *Cache[T]) LoadBy(index *Index[T], get func(load func(keyParts ...any) b
|
||||
}
|
||||
}()
|
||||
|
||||
// Acquire buf.
|
||||
buf := getBuf()
|
||||
// Acquire hasher.
|
||||
h := getHasher()
|
||||
|
||||
// Pass cache check to user func.
|
||||
get(func(keyParts ...any) bool {
|
||||
|
||||
// Reset buf.
|
||||
buf.Reset()
|
||||
h.Reset()
|
||||
|
||||
// Generate index key from provided key parts.
|
||||
if !index.keygen.AppendFromParts(buf, keyParts...) {
|
||||
key, ok := index.hasher.fromParts(h, keyParts...)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// Get temp generated key str,
|
||||
// (not needed after return).
|
||||
keyStr := buf.String()
|
||||
|
||||
// Get all indexed results.
|
||||
list := index.data[keyStr]
|
||||
list := index.data[key]
|
||||
|
||||
if list != nil && list.len > 0 {
|
||||
// Value length before
|
||||
@@ -471,8 +462,8 @@ func (c *Cache[T]) LoadBy(index *Index[T], get func(load func(keyParts ...any) b
|
||||
return false
|
||||
})
|
||||
|
||||
// Done with buf.
|
||||
putBuf(buf)
|
||||
// Done with h.
|
||||
putHasher(h)
|
||||
|
||||
// Done with lock.
|
||||
c.mutex.Unlock()
|
||||
@@ -528,7 +519,7 @@ func (c *Cache[T]) Invalidate(index string, keyParts ...any) {
|
||||
idx := c.Index(index)
|
||||
|
||||
// Generate cache from from provided parts.
|
||||
key, ok := idx.keygen.FromParts(keyParts...)
|
||||
key, ok := idx.hasher.FromParts(keyParts...)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -538,7 +529,7 @@ func (c *Cache[T]) Invalidate(index string, keyParts ...any) {
|
||||
}
|
||||
|
||||
// InvalidateBy invalidates all results stored under index key.
|
||||
func (c *Cache[T]) InvalidateBy(index *Index[T], key string) {
|
||||
func (c *Cache[T]) InvalidateBy(index *Index[T], key uint64) {
|
||||
if index == nil {
|
||||
panic("no index given")
|
||||
}
|
||||
@@ -639,7 +630,7 @@ func (c *Cache[T]) Cap() int {
|
||||
|
||||
// store will store the given value / error result in the cache, storing it under the
|
||||
// already provided index + key if provided, else generating keys from provided value.
|
||||
func (c *Cache[T]) store(index *Index[T], key string, value T, err error) {
|
||||
func (c *Cache[T]) store(index *Index[T], key uint64, value T, err error) {
|
||||
// Acquire new result.
|
||||
res := result_acquire(c)
|
||||
|
||||
@@ -671,8 +662,8 @@ func (c *Cache[T]) store(index *Index[T], key string, value T, err error) {
|
||||
// value, used during cache key gen.
|
||||
rvalue := reflect.ValueOf(value)
|
||||
|
||||
// Acquire buf.
|
||||
buf := getBuf()
|
||||
// Acquire hasher.
|
||||
h := getHasher()
|
||||
|
||||
for i := range c.indices {
|
||||
// Get current index ptr.
|
||||
@@ -684,22 +675,20 @@ func (c *Cache[T]) store(index *Index[T], key string, value T, err error) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Generate key from reflect value,
|
||||
// Generate hash from reflect value,
|
||||
// (this ignores zero value keys).
|
||||
buf.Reset() // reset buf first
|
||||
if !idx.keygen.appendFromRValue(buf, rvalue) {
|
||||
h.Reset() // reset buf first
|
||||
key, ok := idx.hasher.fromRValue(h, rvalue)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Alloc key copy.
|
||||
key := string(buf.B)
|
||||
|
||||
// Append result to index at key.
|
||||
index_append(c, idx, key, res)
|
||||
}
|
||||
|
||||
// Done with buf.
|
||||
putBuf(buf)
|
||||
// Done with h.
|
||||
putHasher(h)
|
||||
}
|
||||
|
||||
if c.lruList.len > c.maxSize {
|
||||
|
370
vendor/codeberg.org/gruf/go-structr/hash.go
generated
vendored
Normal file
370
vendor/codeberg.org/gruf/go-structr/hash.go
generated
vendored
Normal file
@@ -0,0 +1,370 @@
|
||||
package structr
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
|
||||
"github.com/zeebo/xxh3"
|
||||
)
|
||||
|
||||
func hasher(t reflect.Type) func(*xxh3.Hasher, any) bool {
|
||||
switch t.Kind() {
|
||||
case reflect.Int,
|
||||
reflect.Uint,
|
||||
reflect.Uintptr:
|
||||
switch unsafe.Sizeof(int(0)) {
|
||||
case 4:
|
||||
return hash32bit
|
||||
case 8:
|
||||
return hash64bit
|
||||
default:
|
||||
panic("unexpected platform int size")
|
||||
}
|
||||
|
||||
case reflect.Int8,
|
||||
reflect.Uint8:
|
||||
return hash8bit
|
||||
|
||||
case reflect.Int16,
|
||||
reflect.Uint16:
|
||||
return hash16bit
|
||||
|
||||
case reflect.Int32,
|
||||
reflect.Uint32,
|
||||
reflect.Float32:
|
||||
return hash32bit
|
||||
|
||||
case reflect.Int64,
|
||||
reflect.Uint64,
|
||||
reflect.Float64,
|
||||
reflect.Complex64:
|
||||
return hash64bit
|
||||
|
||||
case reflect.String:
|
||||
return hashstring
|
||||
|
||||
case reflect.Pointer:
|
||||
switch t.Elem().Kind() {
|
||||
case reflect.Int,
|
||||
reflect.Uint,
|
||||
reflect.Uintptr:
|
||||
switch unsafe.Sizeof(int(0)) {
|
||||
case 4:
|
||||
return hash32bitptr
|
||||
case 8:
|
||||
return hash64bitptr
|
||||
default:
|
||||
panic("unexpected platform int size")
|
||||
}
|
||||
|
||||
case reflect.Int8,
|
||||
reflect.Uint8:
|
||||
return hash8bitptr
|
||||
|
||||
case reflect.Int16,
|
||||
reflect.Uint16:
|
||||
return hash16bitptr
|
||||
|
||||
case reflect.Int32,
|
||||
reflect.Uint32,
|
||||
reflect.Float32:
|
||||
return hash32bitptr
|
||||
|
||||
case reflect.Int64,
|
||||
reflect.Uint64,
|
||||
reflect.Float64,
|
||||
reflect.Complex64:
|
||||
return hash64bitptr
|
||||
|
||||
case reflect.String:
|
||||
return hashstringptr
|
||||
}
|
||||
|
||||
case reflect.Slice:
|
||||
switch t.Elem().Kind() {
|
||||
case reflect.Int,
|
||||
reflect.Uint,
|
||||
reflect.Uintptr:
|
||||
switch unsafe.Sizeof(int(0)) {
|
||||
case 4:
|
||||
return hash32bitslice
|
||||
case 8:
|
||||
return hash64bitslice
|
||||
default:
|
||||
panic("unexpected platform int size")
|
||||
}
|
||||
|
||||
case reflect.Int8,
|
||||
reflect.Uint8:
|
||||
return hash8bitslice
|
||||
|
||||
case reflect.Int16,
|
||||
reflect.Uint16:
|
||||
return hash16bitslice
|
||||
|
||||
case reflect.Int32,
|
||||
reflect.Uint32,
|
||||
reflect.Float32:
|
||||
return hash32bitslice
|
||||
|
||||
case reflect.Int64,
|
||||
reflect.Uint64,
|
||||
reflect.Float64,
|
||||
reflect.Complex64:
|
||||
return hash64bitslice
|
||||
|
||||
case reflect.String:
|
||||
return hashstringslice
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case t.Implements(reflect.TypeOf((*interface{ MarshalBinary() ([]byte, error) })(nil)).Elem()):
|
||||
return hashbinarymarshaler
|
||||
|
||||
case t.Implements(reflect.TypeOf((*interface{ Bytes() []byte })(nil)).Elem()):
|
||||
return hashbytesmethod
|
||||
|
||||
case t.Implements(reflect.TypeOf((*interface{ String() string })(nil)).Elem()):
|
||||
return hashstringmethod
|
||||
|
||||
case t.Implements(reflect.TypeOf((*interface{ MarshalText() ([]byte, error) })(nil)).Elem()):
|
||||
return hashtextmarshaler
|
||||
|
||||
case t.Implements(reflect.TypeOf((*interface{ MarshalJSON() ([]byte, error) })(nil)).Elem()):
|
||||
return hashjsonmarshaler
|
||||
}
|
||||
panic("unhashable type")
|
||||
}
|
||||
|
||||
func hash8bit(h *xxh3.Hasher, a any) bool {
|
||||
u := *(*uint8)(iface_value(a))
|
||||
_, _ = h.Write([]byte{u})
|
||||
return u == 0
|
||||
}
|
||||
|
||||
func hash8bitptr(h *xxh3.Hasher, a any) bool {
|
||||
u := (*uint8)(iface_value(a))
|
||||
if u == nil {
|
||||
_, _ = h.Write([]byte{
|
||||
0,
|
||||
})
|
||||
return true
|
||||
} else {
|
||||
_, _ = h.Write([]byte{
|
||||
1,
|
||||
byte(*u),
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func hash8bitslice(h *xxh3.Hasher, a any) bool {
|
||||
b := *(*[]byte)(iface_value(a))
|
||||
_, _ = h.Write(b)
|
||||
return b == nil
|
||||
}
|
||||
|
||||
func hash16bit(h *xxh3.Hasher, a any) bool {
|
||||
u := *(*uint16)(iface_value(a))
|
||||
_, _ = h.Write([]byte{
|
||||
byte(u),
|
||||
byte(u >> 8),
|
||||
})
|
||||
return u == 0
|
||||
}
|
||||
|
||||
func hash16bitptr(h *xxh3.Hasher, a any) bool {
|
||||
u := (*uint16)(iface_value(a))
|
||||
if u == nil {
|
||||
_, _ = h.Write([]byte{
|
||||
0,
|
||||
})
|
||||
return true
|
||||
} else {
|
||||
_, _ = h.Write([]byte{
|
||||
1,
|
||||
byte(*u),
|
||||
byte(*u >> 8),
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func hash16bitslice(h *xxh3.Hasher, a any) bool {
|
||||
u := *(*[]uint16)(iface_value(a))
|
||||
for i := range u {
|
||||
_, _ = h.Write([]byte{
|
||||
byte(u[i]),
|
||||
byte(u[i] >> 8),
|
||||
})
|
||||
}
|
||||
return u == nil
|
||||
}
|
||||
|
||||
func hash32bit(h *xxh3.Hasher, a any) bool {
|
||||
u := *(*uint32)(iface_value(a))
|
||||
_, _ = h.Write([]byte{
|
||||
byte(u),
|
||||
byte(u >> 8),
|
||||
byte(u >> 16),
|
||||
byte(u >> 24),
|
||||
})
|
||||
return u == 0
|
||||
}
|
||||
|
||||
func hash32bitptr(h *xxh3.Hasher, a any) bool {
|
||||
u := (*uint32)(iface_value(a))
|
||||
if u == nil {
|
||||
_, _ = h.Write([]byte{
|
||||
0,
|
||||
})
|
||||
return true
|
||||
} else {
|
||||
_, _ = h.Write([]byte{
|
||||
1,
|
||||
byte(*u),
|
||||
byte(*u >> 8),
|
||||
byte(*u >> 16),
|
||||
byte(*u >> 24),
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func hash32bitslice(h *xxh3.Hasher, a any) bool {
|
||||
u := *(*[]uint32)(iface_value(a))
|
||||
for i := range u {
|
||||
_, _ = h.Write([]byte{
|
||||
byte(u[i]),
|
||||
byte(u[i] >> 8),
|
||||
byte(u[i] >> 16),
|
||||
byte(u[i] >> 24),
|
||||
})
|
||||
}
|
||||
return u == nil
|
||||
}
|
||||
|
||||
func hash64bit(h *xxh3.Hasher, a any) bool {
|
||||
u := *(*uint64)(iface_value(a))
|
||||
_, _ = h.Write([]byte{
|
||||
byte(u),
|
||||
byte(u >> 8),
|
||||
byte(u >> 16),
|
||||
byte(u >> 24),
|
||||
byte(u >> 32),
|
||||
byte(u >> 40),
|
||||
byte(u >> 48),
|
||||
byte(u >> 56),
|
||||
})
|
||||
return u == 0
|
||||
}
|
||||
|
||||
func hash64bitptr(h *xxh3.Hasher, a any) bool {
|
||||
u := (*uint64)(iface_value(a))
|
||||
if u == nil {
|
||||
_, _ = h.Write([]byte{
|
||||
0,
|
||||
})
|
||||
return true
|
||||
} else {
|
||||
_, _ = h.Write([]byte{
|
||||
1,
|
||||
byte(*u),
|
||||
byte(*u >> 8),
|
||||
byte(*u >> 16),
|
||||
byte(*u >> 24),
|
||||
byte(*u >> 32),
|
||||
byte(*u >> 40),
|
||||
byte(*u >> 48),
|
||||
byte(*u >> 56),
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func hash64bitslice(h *xxh3.Hasher, a any) bool {
|
||||
u := *(*[]uint64)(iface_value(a))
|
||||
for i := range u {
|
||||
_, _ = h.Write([]byte{
|
||||
byte(u[i]),
|
||||
byte(u[i] >> 8),
|
||||
byte(u[i] >> 16),
|
||||
byte(u[i] >> 24),
|
||||
byte(u[i] >> 32),
|
||||
byte(u[i] >> 40),
|
||||
byte(u[i] >> 48),
|
||||
byte(u[i] >> 56),
|
||||
})
|
||||
}
|
||||
return u == nil
|
||||
}
|
||||
|
||||
func hashstring(h *xxh3.Hasher, a any) bool {
|
||||
s := *(*string)(iface_value(a))
|
||||
_, _ = h.WriteString(s)
|
||||
return s == ""
|
||||
}
|
||||
|
||||
func hashstringptr(h *xxh3.Hasher, a any) bool {
|
||||
s := (*string)(iface_value(a))
|
||||
if s == nil {
|
||||
_, _ = h.Write([]byte{
|
||||
0,
|
||||
})
|
||||
return true
|
||||
} else {
|
||||
_, _ = h.Write([]byte{
|
||||
1,
|
||||
})
|
||||
_, _ = h.WriteString(*s)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func hashstringslice(h *xxh3.Hasher, a any) bool {
|
||||
s := *(*[]string)(iface_value(a))
|
||||
for i := range s {
|
||||
_, _ = h.WriteString(s[i])
|
||||
}
|
||||
return s == nil
|
||||
}
|
||||
|
||||
func hashbinarymarshaler(h *xxh3.Hasher, a any) bool {
|
||||
i := a.(interface{ MarshalBinary() ([]byte, error) })
|
||||
b, _ := i.MarshalBinary()
|
||||
_, _ = h.Write(b)
|
||||
return b == nil
|
||||
}
|
||||
|
||||
func hashbytesmethod(h *xxh3.Hasher, a any) bool {
|
||||
i := a.(interface{ Bytes() []byte })
|
||||
b := i.Bytes()
|
||||
_, _ = h.Write(b)
|
||||
return b == nil
|
||||
}
|
||||
|
||||
func hashstringmethod(h *xxh3.Hasher, a any) bool {
|
||||
i := a.(interface{ String() string })
|
||||
s := i.String()
|
||||
_, _ = h.WriteString(s)
|
||||
return s == ""
|
||||
}
|
||||
|
||||
func hashtextmarshaler(h *xxh3.Hasher, a any) bool {
|
||||
i := a.(interface{ MarshalText() ([]byte, error) })
|
||||
b, _ := i.MarshalText()
|
||||
_, _ = h.Write(b)
|
||||
return b == nil
|
||||
}
|
||||
|
||||
func hashjsonmarshaler(h *xxh3.Hasher, a any) bool {
|
||||
i := a.(interface{ MarshalJSON() ([]byte, error) })
|
||||
b, _ := i.MarshalJSON()
|
||||
_, _ = h.Write(b)
|
||||
return b == nil
|
||||
}
|
||||
|
||||
func iface_value(a any) unsafe.Pointer {
|
||||
type eface struct{ _, v unsafe.Pointer }
|
||||
return (*eface)(unsafe.Pointer(&a)).v
|
||||
}
|
176
vendor/codeberg.org/gruf/go-structr/hasher.go
generated
vendored
Normal file
176
vendor/codeberg.org/gruf/go-structr/hasher.go
generated
vendored
Normal file
@@ -0,0 +1,176 @@
|
||||
package structr
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/zeebo/xxh3"
|
||||
)
|
||||
|
||||
// Hasher provides hash checksumming for a configured
|
||||
// index, based on an arbitrary combination of generic
|
||||
// paramter struct type's fields. This provides hashing
|
||||
// both by input of the fields separately, or passing
|
||||
// an instance of the generic paramter struct type.
|
||||
//
|
||||
// Supported field types by the hasher include:
|
||||
// - ~int
|
||||
// - ~int8
|
||||
// - ~int16
|
||||
// - ~int32
|
||||
// - ~int64
|
||||
// - ~float32
|
||||
// - ~float64
|
||||
// - ~string
|
||||
// - slices / ptrs of the above
|
||||
type Hasher[StructType any] struct {
|
||||
|
||||
// fields contains our representation
|
||||
// of struct fields contained in the
|
||||
// creation of sums by this hasher.
|
||||
fields []structfield
|
||||
|
||||
// zero specifies whether zero
|
||||
// value fields are permitted.
|
||||
zero bool
|
||||
}
|
||||
|
||||
// NewHasher returns a new initialized Hasher for the receiving generic
|
||||
// parameter type, comprising of the given field strings, and whether to
|
||||
// allow zero values to be incldued within generated hash checksum values.
|
||||
func NewHasher[T any](fields []string, allowZero bool) Hasher[T] {
|
||||
var h Hasher[T]
|
||||
|
||||
// Preallocate expected struct field slice.
|
||||
h.fields = make([]structfield, len(fields))
|
||||
|
||||
// Get the reflected struct ptr type.
|
||||
t := reflect.TypeOf((*T)(nil)).Elem()
|
||||
|
||||
for i, fieldName := range fields {
|
||||
// Split name to account for nesting.
|
||||
names := strings.Split(fieldName, ".")
|
||||
|
||||
// Look for a usable struct field from type.
|
||||
sfield, ok := findField(t, names, allowZero)
|
||||
if !ok {
|
||||
panicf("failed finding field: %s", fieldName)
|
||||
}
|
||||
|
||||
// Set parsed struct field.
|
||||
h.fields[i] = sfield
|
||||
}
|
||||
|
||||
// Set config flags.
|
||||
h.zero = allowZero
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// FromParts generates hash checksum (used as index key) from individual key parts.
|
||||
func (h *Hasher[T]) FromParts(parts ...any) (sum uint64, ok bool) {
|
||||
hh := getHasher()
|
||||
sum, ok = h.fromParts(hh, parts...)
|
||||
putHasher(hh)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func (h *Hasher[T]) fromParts(hh *xxh3.Hasher, parts ...any) (sum uint64, ok bool) {
|
||||
if len(parts) != len(h.fields) {
|
||||
// User must provide correct number of parts for key.
|
||||
panicf("incorrect number key parts: want=%d received=%d",
|
||||
len(parts),
|
||||
len(h.fields),
|
||||
)
|
||||
}
|
||||
|
||||
if h.zero {
|
||||
// Zero values are permitted,
|
||||
// mangle all values and ignore
|
||||
// zero value return booleans.
|
||||
for i, part := range parts {
|
||||
|
||||
// Write mangled part to hasher.
|
||||
_ = h.fields[i].hasher(hh, part)
|
||||
}
|
||||
} else {
|
||||
// Zero values are NOT permitted.
|
||||
for i, part := range parts {
|
||||
|
||||
// Write mangled field to hasher.
|
||||
z := h.fields[i].hasher(hh, part)
|
||||
|
||||
if z {
|
||||
// The value was zero for
|
||||
// this type, return early.
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hh.Sum64(), true
|
||||
}
|
||||
|
||||
// FromValue generates hash checksum (used as index key) from a value, via reflection.
|
||||
func (h *Hasher[T]) FromValue(value T) (sum uint64, ok bool) {
|
||||
rvalue := reflect.ValueOf(value)
|
||||
hh := getHasher()
|
||||
sum, ok = h.fromRValue(hh, rvalue)
|
||||
putHasher(hh)
|
||||
return
|
||||
}
|
||||
|
||||
func (h *Hasher[T]) fromRValue(hh *xxh3.Hasher, rvalue reflect.Value) (uint64, bool) {
|
||||
// Follow any ptrs leading to value.
|
||||
for rvalue.Kind() == reflect.Pointer {
|
||||
rvalue = rvalue.Elem()
|
||||
}
|
||||
|
||||
if h.zero {
|
||||
// Zero values are permitted,
|
||||
// mangle all values and ignore
|
||||
// zero value return booleans.
|
||||
for i := range h.fields {
|
||||
|
||||
// Get the reflect value's field at idx.
|
||||
fv := rvalue.FieldByIndex(h.fields[i].index)
|
||||
fi := fv.Interface()
|
||||
|
||||
// Write mangled field to hasher.
|
||||
_ = h.fields[i].hasher(hh, fi)
|
||||
}
|
||||
} else {
|
||||
// Zero values are NOT permitted.
|
||||
for i := range h.fields {
|
||||
|
||||
// Get the reflect value's field at idx.
|
||||
fv := rvalue.FieldByIndex(h.fields[i].index)
|
||||
fi := fv.Interface()
|
||||
|
||||
// Write mangled field to hasher.
|
||||
z := h.fields[i].hasher(hh, fi)
|
||||
|
||||
if z {
|
||||
// The value was zero for
|
||||
// this type, return early.
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hh.Sum64(), true
|
||||
}
|
||||
|
||||
type structfield struct {
|
||||
// index is the reflected index
|
||||
// of this field (this takes into
|
||||
// account struct nesting).
|
||||
index []int
|
||||
|
||||
// hasher is the relevant function
|
||||
// for hashing value of structfield
|
||||
// into the supplied hashbuf, where
|
||||
// return value indicates if zero.
|
||||
hasher func(*xxh3.Hasher, any) bool
|
||||
}
|
26
vendor/codeberg.org/gruf/go-structr/index.go
generated
vendored
26
vendor/codeberg.org/gruf/go-structr/index.go
generated
vendored
@@ -45,12 +45,12 @@ type Index[StructType any] struct {
|
||||
// string value of contained fields.
|
||||
name string
|
||||
|
||||
// struct field key serializer.
|
||||
keygen KeyGen[StructType]
|
||||
// struct field key hasher.
|
||||
hasher Hasher[StructType]
|
||||
|
||||
// backing in-memory data store of
|
||||
// generated index keys to result lists.
|
||||
data map[string]*list[*result[StructType]]
|
||||
data map[uint64]*list[*result[StructType]]
|
||||
|
||||
// whether to allow
|
||||
// multiple results
|
||||
@@ -59,20 +59,20 @@ type Index[StructType any] struct {
|
||||
}
|
||||
|
||||
// init initializes this index with the given configuration.
|
||||
func (i *Index[T]) init(config IndexConfig) {
|
||||
func (i *Index[T]) init(config IndexConfig, max int) {
|
||||
fields := strings.Split(config.Fields, ",")
|
||||
i.name = config.Fields
|
||||
i.keygen = NewKeyGen[T](fields, config.AllowZero)
|
||||
i.hasher = NewHasher[T](fields, config.AllowZero)
|
||||
i.unique = !config.Multiple
|
||||
i.data = make(map[string]*list[*result[T]])
|
||||
i.data = make(map[uint64]*list[*result[T]], max+1)
|
||||
}
|
||||
|
||||
// KeyGen returns the key generator associated with this index.
|
||||
func (i *Index[T]) KeyGen() *KeyGen[T] {
|
||||
return &i.keygen
|
||||
// Hasher returns the hash checksummer associated with this index.
|
||||
func (i *Index[T]) Hasher() *Hasher[T] {
|
||||
return &i.hasher
|
||||
}
|
||||
|
||||
func index_append[T any](c *Cache[T], i *Index[T], key string, res *result[T]) {
|
||||
func index_append[T any](c *Cache[T], i *Index[T], key uint64, res *result[T]) {
|
||||
// Acquire + setup indexkey.
|
||||
ikey := indexkey_acquire(c)
|
||||
ikey.entry.Value = res
|
||||
@@ -138,7 +138,7 @@ func index_deleteOne[T any](c *Cache[T], i *Index[T], ikey *indexkey[T]) {
|
||||
}
|
||||
}
|
||||
|
||||
func index_delete[T any](c *Cache[T], i *Index[T], key string, fn func(*result[T])) {
|
||||
func index_delete[T any](c *Cache[T], i *Index[T], key uint64, fn func(*result[T])) {
|
||||
if fn == nil {
|
||||
panic("nil fn")
|
||||
}
|
||||
@@ -180,7 +180,7 @@ type indexkey[T any] struct {
|
||||
// key is the generated index key
|
||||
// the related result is indexed
|
||||
// under, in the below index.
|
||||
key string
|
||||
key uint64
|
||||
|
||||
// index is the index that the
|
||||
// related result is indexed in.
|
||||
@@ -205,7 +205,7 @@ func indexkey_acquire[T any](c *Cache[T]) *indexkey[T] {
|
||||
func indexkey_release[T any](c *Cache[T], ikey *indexkey[T]) {
|
||||
// Reset indexkey.
|
||||
ikey.entry.Value = nil
|
||||
ikey.key = ""
|
||||
ikey.key = 0
|
||||
ikey.index = nil
|
||||
|
||||
// Release indexkey to memory pool.
|
||||
|
204
vendor/codeberg.org/gruf/go-structr/key.go
generated
vendored
204
vendor/codeberg.org/gruf/go-structr/key.go
generated
vendored
@@ -1,204 +0,0 @@
|
||||
package structr
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"codeberg.org/gruf/go-byteutil"
|
||||
"codeberg.org/gruf/go-mangler"
|
||||
)
|
||||
|
||||
// KeyGen is the underlying index key generator
|
||||
// used within Index, and therefore Cache itself.
|
||||
type KeyGen[StructType any] struct {
|
||||
|
||||
// fields contains our representation of
|
||||
// the struct fields contained in the
|
||||
// creation of keys by this generator.
|
||||
fields []structfield
|
||||
|
||||
// zero specifies whether zero
|
||||
// value fields are permitted.
|
||||
zero bool
|
||||
}
|
||||
|
||||
// NewKeyGen returns a new initialized KeyGen for the receiving generic
|
||||
// parameter type, comprising of the given field strings, and whether to
|
||||
// allow zero values to be included within generated output strings.
|
||||
func NewKeyGen[T any](fields []string, allowZero bool) KeyGen[T] {
|
||||
var kgen KeyGen[T]
|
||||
|
||||
// Preallocate expected struct field slice.
|
||||
kgen.fields = make([]structfield, len(fields))
|
||||
|
||||
// Get the reflected struct ptr type.
|
||||
t := reflect.TypeOf((*T)(nil)).Elem()
|
||||
|
||||
for i, fieldName := range fields {
|
||||
// Split name to account for nesting.
|
||||
names := strings.Split(fieldName, ".")
|
||||
|
||||
// Look for a usable struct field from type.
|
||||
sfield, ok := findField(t, names, allowZero)
|
||||
if !ok {
|
||||
panicf("failed finding field: %s", fieldName)
|
||||
}
|
||||
|
||||
// Set parsed struct field.
|
||||
kgen.fields[i] = sfield
|
||||
}
|
||||
|
||||
// Set config flags.
|
||||
kgen.zero = allowZero
|
||||
|
||||
return kgen
|
||||
}
|
||||
|
||||
// FromParts generates key string from individual key parts.
|
||||
func (kgen *KeyGen[T]) FromParts(parts ...any) (key string, ok bool) {
|
||||
buf := getBuf()
|
||||
if ok = kgen.AppendFromParts(buf, parts...); ok {
|
||||
key = string(buf.B)
|
||||
}
|
||||
putBuf(buf)
|
||||
return
|
||||
}
|
||||
|
||||
// FromValue generates key string from a value, via reflection.
|
||||
func (kgen *KeyGen[T]) FromValue(value T) (key string, ok bool) {
|
||||
buf := getBuf()
|
||||
rvalue := reflect.ValueOf(value)
|
||||
if ok = kgen.appendFromRValue(buf, rvalue); ok {
|
||||
key = string(buf.B)
|
||||
}
|
||||
putBuf(buf)
|
||||
return
|
||||
}
|
||||
|
||||
// AppendFromParts generates key string into provided buffer, from individual key parts.
|
||||
func (kgen *KeyGen[T]) AppendFromParts(buf *byteutil.Buffer, parts ...any) bool {
|
||||
if len(parts) != len(kgen.fields) {
|
||||
// User must provide correct number of parts for key.
|
||||
panicf("incorrect number key parts: want=%d received=%d",
|
||||
len(parts),
|
||||
len(kgen.fields),
|
||||
)
|
||||
}
|
||||
|
||||
if kgen.zero {
|
||||
// Zero values are permitted,
|
||||
// mangle all values and ignore
|
||||
// zero value return booleans.
|
||||
for i, part := range parts {
|
||||
|
||||
// Mangle this value into buffer.
|
||||
_ = kgen.fields[i].Mangle(buf, part)
|
||||
|
||||
// Append part separator.
|
||||
buf.B = append(buf.B, '.')
|
||||
}
|
||||
} else {
|
||||
// Zero values are NOT permitted.
|
||||
for i, part := range parts {
|
||||
|
||||
// Mangle this value into buffer.
|
||||
z := kgen.fields[i].Mangle(buf, part)
|
||||
|
||||
if z {
|
||||
// The value was zero for
|
||||
// this type, return early.
|
||||
return false
|
||||
}
|
||||
|
||||
// Append part separator.
|
||||
buf.B = append(buf.B, '.')
|
||||
}
|
||||
}
|
||||
|
||||
// Drop the last separator.
|
||||
buf.B = buf.B[:len(buf.B)-1]
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// AppendFromValue generates key string into provided buffer, from a value via reflection.
|
||||
func (kgen *KeyGen[T]) AppendFromValue(buf *byteutil.Buffer, value T) bool {
|
||||
return kgen.appendFromRValue(buf, reflect.ValueOf(value))
|
||||
}
|
||||
|
||||
// appendFromRValue is the underlying generator function for the exported ___FromValue() functions,
|
||||
// accepting a reflected input. We do not expose this as the reflected value is EXPECTED to be right.
|
||||
func (kgen *KeyGen[T]) appendFromRValue(buf *byteutil.Buffer, rvalue reflect.Value) bool {
|
||||
// Follow any ptrs leading to value.
|
||||
for rvalue.Kind() == reflect.Pointer {
|
||||
rvalue = rvalue.Elem()
|
||||
}
|
||||
|
||||
if kgen.zero {
|
||||
// Zero values are permitted,
|
||||
// mangle all values and ignore
|
||||
// zero value return booleans.
|
||||
for i := range kgen.fields {
|
||||
|
||||
// Get the reflect value's field at idx.
|
||||
fv := rvalue.FieldByIndex(kgen.fields[i].index)
|
||||
fi := fv.Interface()
|
||||
|
||||
// Mangle this value into buffer.
|
||||
_ = kgen.fields[i].Mangle(buf, fi)
|
||||
|
||||
// Append part separator.
|
||||
buf.B = append(buf.B, '.')
|
||||
}
|
||||
} else {
|
||||
// Zero values are NOT permitted.
|
||||
for i := range kgen.fields {
|
||||
|
||||
// Get the reflect value's field at idx.
|
||||
fv := rvalue.FieldByIndex(kgen.fields[i].index)
|
||||
fi := fv.Interface()
|
||||
|
||||
// Mangle this value into buffer.
|
||||
z := kgen.fields[i].Mangle(buf, fi)
|
||||
|
||||
if z {
|
||||
// The value was zero for
|
||||
// this type, return early.
|
||||
return false
|
||||
}
|
||||
|
||||
// Append part separator.
|
||||
buf.B = append(buf.B, '.')
|
||||
}
|
||||
}
|
||||
|
||||
// Drop the last separator.
|
||||
buf.B = buf.B[:len(buf.B)-1]
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
type structfield struct {
|
||||
// index is the reflected index
|
||||
// of this field (this takes into
|
||||
// account struct nesting).
|
||||
index []int
|
||||
|
||||
// zero is the possible mangled
|
||||
// zero value for this field.
|
||||
zero string
|
||||
|
||||
// mangler is the mangler function for
|
||||
// serializing values of this field.
|
||||
mangler mangler.Mangler
|
||||
}
|
||||
|
||||
// Mangle mangles the given value, using the determined type-appropriate
|
||||
// field's type. The returned boolean indicates whether this is a zero value.
|
||||
func (f *structfield) Mangle(buf *byteutil.Buffer, value any) (isZero bool) {
|
||||
s := len(buf.B) // start pos.
|
||||
buf.B = f.mangler(buf.B, value)
|
||||
e := len(buf.B) // end pos.
|
||||
isZero = (f.zero == string(buf.B[s:e]))
|
||||
return
|
||||
}
|
50
vendor/codeberg.org/gruf/go-structr/util.go
generated
vendored
50
vendor/codeberg.org/gruf/go-structr/util.go
generated
vendored
@@ -7,8 +7,7 @@ import (
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"codeberg.org/gruf/go-byteutil"
|
||||
"codeberg.org/gruf/go-mangler"
|
||||
"github.com/zeebo/xxh3"
|
||||
)
|
||||
|
||||
// findField will search for a struct field with given set of names, where names is a len > 0 slice of names account for nesting.
|
||||
@@ -68,22 +67,8 @@ func findField(t reflect.Type, names []string, allowZero bool) (sfield structfie
|
||||
t = field.Type
|
||||
}
|
||||
|
||||
// Get final type mangler func.
|
||||
sfield.mangler = mangler.Get(t)
|
||||
|
||||
if allowZero {
|
||||
var buf []byte
|
||||
|
||||
// Allocate field instance.
|
||||
v := reflect.New(field.Type)
|
||||
v = v.Elem()
|
||||
|
||||
// Serialize this zero value into buf.
|
||||
buf = sfield.mangler(buf, v.Interface())
|
||||
|
||||
// Set zero value str.
|
||||
sfield.zero = string(buf)
|
||||
}
|
||||
// Get final type hash func.
|
||||
sfield.hasher = hasher(t)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -93,26 +78,21 @@ func panicf(format string, args ...any) {
|
||||
panic(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// bufpool provides a memory pool of byte
|
||||
// buffers used when encoding key types.
|
||||
var bufPool sync.Pool
|
||||
// hashPool provides a memory pool of xxh3
|
||||
// hasher objects used indexing field vals.
|
||||
var hashPool sync.Pool
|
||||
|
||||
// getBuf fetches buffer from memory pool.
|
||||
func getBuf() *byteutil.Buffer {
|
||||
v := bufPool.Get()
|
||||
// gethashbuf fetches hasher from memory pool.
|
||||
func getHasher() *xxh3.Hasher {
|
||||
v := hashPool.Get()
|
||||
if v == nil {
|
||||
buf := new(byteutil.Buffer)
|
||||
buf.B = make([]byte, 0, 512)
|
||||
v = buf
|
||||
v = new(xxh3.Hasher)
|
||||
}
|
||||
return v.(*byteutil.Buffer)
|
||||
return v.(*xxh3.Hasher)
|
||||
}
|
||||
|
||||
// putBuf replaces buffer in memory pool.
|
||||
func putBuf(buf *byteutil.Buffer) {
|
||||
if buf.Cap() > int(^uint16(0)) {
|
||||
return // drop large bufs
|
||||
}
|
||||
buf.Reset()
|
||||
bufPool.Put(buf)
|
||||
// putHasher replaces hasher in memory pool.
|
||||
func putHasher(h *xxh3.Hasher) {
|
||||
h.Reset()
|
||||
hashPool.Put(h)
|
||||
}
|
||||
|
Reference in New Issue
Block a user