2022-09-28 07:39:01 +00:00
|
|
|
package bytesutil
|
|
|
|
|
|
|
|
import (
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"sync/atomic"
|
2022-12-12 22:31:16 +00:00
|
|
|
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
2022-09-28 07:39:01 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// FastStringTransformer implements fast transformer for strings.
|
|
|
|
//
|
|
|
|
// It caches transformed strings and returns them back on the next calls
|
|
|
|
// without calling the transformFunc, which may be expensive.
|
|
|
|
type FastStringTransformer struct {
|
2022-12-12 22:31:16 +00:00
|
|
|
lastCleanupTime uint64
|
|
|
|
|
|
|
|
m sync.Map
|
2022-09-28 07:39:01 +00:00
|
|
|
|
|
|
|
transformFunc func(s string) string
|
|
|
|
}
|
|
|
|
|
2022-12-12 22:31:16 +00:00
|
|
|
type fstEntry struct {
|
|
|
|
lastAccessTime uint64
|
|
|
|
s string
|
|
|
|
}
|
|
|
|
|
2022-09-28 07:39:01 +00:00
|
|
|
// NewFastStringTransformer creates new transformer, which applies transformFunc to strings passed to Transform()
|
|
|
|
//
|
|
|
|
// transformFunc must return the same result for the same input.
|
|
|
|
func NewFastStringTransformer(transformFunc func(s string) string) *FastStringTransformer {
|
2022-12-12 22:31:16 +00:00
|
|
|
return &FastStringTransformer{
|
|
|
|
lastCleanupTime: fasttime.UnixTimestamp(),
|
|
|
|
transformFunc: transformFunc,
|
|
|
|
}
|
2022-09-28 07:39:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Transform applies transformFunc to s and returns the result.
|
|
|
|
func (fst *FastStringTransformer) Transform(s string) string {
|
2022-12-12 22:31:16 +00:00
|
|
|
ct := fasttime.UnixTimestamp()
|
|
|
|
v, ok := fst.m.Load(s)
|
2022-09-28 07:39:01 +00:00
|
|
|
if ok {
|
|
|
|
// Fast path - the transformed s is found in the cache.
|
2022-12-12 22:31:16 +00:00
|
|
|
e := v.(*fstEntry)
|
|
|
|
if atomic.LoadUint64(&e.lastAccessTime)+10 < ct {
|
|
|
|
// Reduce the frequency of e.lastAccessTime update to once per 10 seconds
|
|
|
|
// in order to improve the fast path speed on systems with many CPU cores.
|
|
|
|
atomic.StoreUint64(&e.lastAccessTime, ct)
|
|
|
|
}
|
|
|
|
return e.s
|
2022-09-28 07:39:01 +00:00
|
|
|
}
|
|
|
|
// Slow path - transform s and store it in the cache.
|
|
|
|
sTransformed := fst.transformFunc(s)
|
|
|
|
// Make a copy of s in order to limit memory usage to the s length,
|
|
|
|
// since the s may point to bigger string.
|
2022-10-14 06:49:53 +00:00
|
|
|
// This also protects from the case when s contains unsafe string, which points to a temporary byte slice.
|
|
|
|
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3227
|
2022-09-28 07:39:01 +00:00
|
|
|
s = strings.Clone(s)
|
|
|
|
if sTransformed == s {
|
|
|
|
// point sTransformed to just allocated s, since it may point to s,
|
|
|
|
// which, in turn, can point to bigger string.
|
|
|
|
sTransformed = s
|
|
|
|
}
|
2022-12-12 22:31:16 +00:00
|
|
|
e := &fstEntry{
|
|
|
|
lastAccessTime: ct,
|
|
|
|
s: sTransformed,
|
|
|
|
}
|
|
|
|
fst.m.Store(s, e)
|
|
|
|
|
2022-12-21 20:57:28 +00:00
|
|
|
if needCleanup(&fst.lastCleanupTime, ct) {
|
|
|
|
// Perform a global cleanup for fst.m by removing items, which weren't accessed during the last 5 minutes.
|
2022-12-12 22:31:16 +00:00
|
|
|
m := &fst.m
|
|
|
|
m.Range(func(k, v interface{}) bool {
|
|
|
|
e := v.(*fstEntry)
|
|
|
|
if atomic.LoadUint64(&e.lastAccessTime)+5*60 < ct {
|
|
|
|
m.Delete(k)
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
2022-09-28 07:39:01 +00:00
|
|
|
}
|
2022-12-12 22:31:16 +00:00
|
|
|
|
2022-09-28 07:39:01 +00:00
|
|
|
return sTransformed
|
|
|
|
}
|