mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
7e1dd8ab9d
See ea9e2b19a5
95 lines
2.6 KiB
Go
95 lines
2.6 KiB
Go
package bytesutil
|
|
|
|
import (
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
|
)
|
|
|
|
// FastStringMatcher implements fast matcher for strings.
|
|
//
|
|
// It caches string match results and returns them back on the next calls
|
|
// without calling the matchFunc, which may be expensive.
|
|
type FastStringMatcher struct {
|
|
lastCleanupTime atomic.Uint64
|
|
|
|
m sync.Map
|
|
|
|
matchFunc func(s string) bool
|
|
}
|
|
|
|
type fsmEntry struct {
|
|
lastAccessTime atomic.Uint64
|
|
ok bool
|
|
}
|
|
|
|
// NewFastStringMatcher creates new matcher, which applies matchFunc to strings passed to Match()
|
|
//
|
|
// matchFunc must return the same result for the same input.
|
|
func NewFastStringMatcher(matchFunc func(s string) bool) *FastStringMatcher {
|
|
fsm := &FastStringMatcher{
|
|
matchFunc: matchFunc,
|
|
}
|
|
fsm.lastCleanupTime.Store(fasttime.UnixTimestamp())
|
|
return fsm
|
|
}
|
|
|
|
// Match applies matchFunc to s and returns the result.
|
|
func (fsm *FastStringMatcher) Match(s string) bool {
|
|
if isSkipCache(s) {
|
|
return fsm.matchFunc(s)
|
|
}
|
|
|
|
ct := fasttime.UnixTimestamp()
|
|
v, ok := fsm.m.Load(s)
|
|
if ok {
|
|
// Fast path - s match result is found in the cache.
|
|
e := v.(*fsmEntry)
|
|
if e.lastAccessTime.Load()+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.
|
|
e.lastAccessTime.Store(ct)
|
|
}
|
|
return e.ok
|
|
}
|
|
// Slow path - run matchFunc for s and store the result in the cache.
|
|
b := fsm.matchFunc(s)
|
|
e := &fsmEntry{
|
|
ok: b,
|
|
}
|
|
e.lastAccessTime.Store(ct)
|
|
// Make a copy of s in order to limit memory usage to the s length,
|
|
// since the s may point to bigger string.
|
|
// 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
|
|
s = strings.Clone(s)
|
|
fsm.m.Store(s, e)
|
|
|
|
if needCleanup(&fsm.lastCleanupTime, ct) {
|
|
// Perform a global cleanup for fsm.m by removing items, which weren't accessed during the last 5 minutes.
|
|
m := &fsm.m
|
|
deadline := ct - uint64(cacheExpireDuration.Seconds())
|
|
m.Range(func(k, v interface{}) bool {
|
|
e := v.(*fsmEntry)
|
|
if e.lastAccessTime.Load() < deadline {
|
|
m.Delete(k)
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
|
|
return b
|
|
}
|
|
|
|
func needCleanup(lastCleanupTime *atomic.Uint64, currentTime uint64) bool {
|
|
lct := lastCleanupTime.Load()
|
|
if lct+61 >= currentTime {
|
|
return false
|
|
}
|
|
// Atomically compare and swap the current time with the lastCleanupTime
|
|
// in order to guarantee that only a single goroutine out of multiple
|
|
// concurrently executing goroutines gets true from the call.
|
|
return lastCleanupTime.CompareAndSwap(lct, currentTime)
|
|
}
|