mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
lib/bytesutil: optimize internStringMap cleanup
- Make it in a separate goroutine, so it doesn't slow down regular intern() calls. - Do not lock internStringMap.mutableLock during the cleanup routine, since now it is called from a single goroutine and reads only the readonly part of the internStringMap. This should prevent from locking regular intern() calls for new strings during cleanups. - Add jitter to the cleanup interval in order to prevent from synchornous increase in resource usage during cleanups. - Run the cleanup twice per -internStringCacheExpireDuration . This should save 30% CPU time spent on cleanup comparing to the previous code, which was running the cleanup 3 times per -internStringCacheExpireDuration .
This commit is contained in:
parent
34071ac660
commit
d54840f2f2
1 changed files with 14 additions and 17 deletions
|
@ -8,6 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeutil"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -20,14 +21,11 @@ var (
|
|||
)
|
||||
|
||||
type internStringMap struct {
|
||||
mu sync.Mutex
|
||||
mutableLock sync.Mutex
|
||||
mutable map[string]string
|
||||
mutableReads uint64
|
||||
|
||||
readonly atomic.Pointer[map[string]internStringMapEntry]
|
||||
|
||||
cleanupInterval uint64
|
||||
nextCleanupTime atomic.Uint64
|
||||
}
|
||||
|
||||
type internStringMapEntry struct {
|
||||
|
@ -41,8 +39,15 @@ func newInternStringMap() *internStringMap {
|
|||
}
|
||||
readonly := make(map[string]internStringMapEntry)
|
||||
m.readonly.Store(&readonly)
|
||||
m.cleanupInterval = uint64(cacheExpireDuration.Seconds() / 3)
|
||||
m.nextCleanupTime.Store(fasttime.UnixTimestamp() + m.cleanupInterval)
|
||||
|
||||
go func() {
|
||||
cleanupInterval := timeutil.AddJitterToDuration(*cacheExpireDuration) / 2
|
||||
ticker := time.NewTicker(cleanupInterval)
|
||||
for range ticker.C {
|
||||
m.cleanup()
|
||||
}
|
||||
}()
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
|
@ -54,21 +59,16 @@ func (m *internStringMap) intern(s string) string {
|
|||
if isSkipCache(s) {
|
||||
return strings.Clone(s)
|
||||
}
|
||||
currentTime := fasttime.UnixTimestamp()
|
||||
if currentTime >= m.nextCleanupTime.Load() {
|
||||
m.nextCleanupTime.Store(currentTime + m.cleanupInterval)
|
||||
m.cleanup()
|
||||
}
|
||||
|
||||
readonly := m.getReadonly()
|
||||
e, ok := readonly[s]
|
||||
if ok {
|
||||
// Fast path - the string has been found in readonly map
|
||||
// Fast path - the string has been found in readonly map.
|
||||
return e.s
|
||||
}
|
||||
|
||||
// Slower path - search for the string in mutable map under the lock.
|
||||
m.mu.Lock()
|
||||
m.mutableLock.Lock()
|
||||
sInterned, ok := m.mutable[s]
|
||||
if !ok {
|
||||
// Verify whether s has been already registered by concurrent goroutines in m.readonly
|
||||
|
@ -88,7 +88,7 @@ func (m *internStringMap) intern(s string) string {
|
|||
m.migrateMutableToReadonlyLocked()
|
||||
m.mutableReads = 0
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.mutableLock.Unlock()
|
||||
|
||||
return sInterned
|
||||
}
|
||||
|
@ -111,9 +111,6 @@ func (m *internStringMap) migrateMutableToReadonlyLocked() {
|
|||
}
|
||||
|
||||
func (m *internStringMap) cleanup() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
readonly := m.getReadonly()
|
||||
currentTime := fasttime.UnixTimestamp()
|
||||
needCleanup := false
|
||||
|
|
Loading…
Reference in a new issue