1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
|
package decoder
import (
"sync"
)
// cacheEntry represents a cached string with its offset and dedicated mutex.
type cacheEntry struct {
str string
offset uint
mu sync.RWMutex
}
// stringCache provides bounded string interning with per-entry mutexes for minimal contention.
// This achieves thread safety while avoiding the global lock bottleneck.
type stringCache struct {
entries [512]cacheEntry
}
// newStringCache creates a new per-entry mutex-based string cache.
func newStringCache() *stringCache {
return &stringCache{}
}
// internAt returns a canonical string for the data at the given offset and size.
// Uses per-entry RWMutex for fine-grained thread safety with minimal contention.
func (sc *stringCache) internAt(offset, size uint, data []byte) string {
const (
minCachedLen = 2 // single byte strings not worth caching
maxCachedLen = 100 // reasonable upper bound for geographic strings
)
// Skip caching for very short or very long strings
if size < minCachedLen || size > maxCachedLen {
return string(data[offset : offset+size])
}
// Use same cache index calculation as original: offset % cacheSize
i := offset % uint(len(sc.entries))
entry := &sc.entries[i]
// Fast path: read lock and check
entry.mu.RLock()
if entry.offset == offset && entry.str != "" {
str := entry.str
entry.mu.RUnlock()
return str
}
entry.mu.RUnlock()
// Cache miss - create new string
str := string(data[offset : offset+size])
// Store with write lock on this specific entry
entry.mu.Lock()
entry.offset = offset
entry.str = str
entry.mu.Unlock()
return str
}
|