diff --git a/src/cache.go b/src/cache.go index ced2a26c..39d4250d 100644 --- a/src/cache.go +++ b/src/cache.go @@ -16,6 +16,12 @@ func NewChunkCache() *ChunkCache { return &ChunkCache{sync.Mutex{}, make(map[*Chunk]*queryCache)} } +func (cc *ChunkCache) Clear() { + cc.mutex.Lock() + cc.cache = make(map[*Chunk]*queryCache) + cc.mutex.Unlock() +} + // Add adds the list to the cache func (cc *ChunkCache) Add(chunk *Chunk, key string, list []Result) { if len(key) == 0 || !chunk.IsFull() || len(list) > queryCacheMax { diff --git a/src/core.go b/src/core.go index c69ba629..dbae6a69 100644 --- a/src/core.go +++ b/src/core.go @@ -37,7 +37,6 @@ func Run(opts *Options) (int, error) { return ExitError, err } - defer clearCaches() defer util.RunAtExitFuncs() // Output channel given @@ -154,14 +153,16 @@ func Run(opts *Options) (int, error) { forward = true } } + cache := NewChunkCache() + patternCache := make(map[string]*Pattern) patternBuilder := func(runes []rune) *Pattern { - return BuildPattern( + return BuildPattern(cache, patternCache, opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos, opts.Filter == nil, opts.Nth, opts.Delimiter, runes) } inputRevision := 0 snapshotRevision := 0 - matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox, inputRevision) + matcher := NewMatcher(cache, patternBuilder, sort, opts.Tac, eventBox, inputRevision) // Filtering mode if opts.Filter != nil { diff --git a/src/matcher.go b/src/matcher.go index 372cdc4f..26426e4f 100644 --- a/src/matcher.go +++ b/src/matcher.go @@ -21,6 +21,7 @@ type MatchRequest struct { // Matcher is responsible for performing search type Matcher struct { + cache *ChunkCache patternBuilder func([]rune) *Pattern sort bool tac bool @@ -38,10 +39,11 @@ const ( ) // NewMatcher returns a new Matcher -func NewMatcher(patternBuilder func([]rune) *Pattern, +func NewMatcher(cache *ChunkCache, patternBuilder func([]rune) *Pattern, sort bool, tac bool, eventBox *util.EventBox, revision int) *Matcher { partitions := util.Min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions) return &Matcher{ + cache: cache, patternBuilder: patternBuilder, sort: sort, tac: tac, @@ -84,7 +86,7 @@ func (m *Matcher) Loop() { m.sort = request.sort m.revision = request.revision m.mergerCache = make(map[string]*Merger) - clearChunkCache() + m.cache.Clear() } // Restart search diff --git a/src/pattern.go b/src/pattern.go index 8f6ba0ae..cbe73dc4 100644 --- a/src/pattern.go +++ b/src/pattern.go @@ -63,34 +63,14 @@ type Pattern struct { cache *ChunkCache } -var ( - _patternCache map[string]*Pattern - _splitRegex *regexp.Regexp - _cache *ChunkCache -) +var _splitRegex *regexp.Regexp func init() { _splitRegex = regexp.MustCompile(" +") - clearCaches() -} - -func clearCaches() { - clearPatternCache() - clearChunkCache() -} - -func clearPatternCache() { - // We can uniquely identify the pattern for a given string since - // search mode and caseMode do not change while the program is running - _patternCache = make(map[string]*Pattern) -} - -func clearChunkCache() { - _cache = NewChunkCache() } // BuildPattern builds Pattern object from the given arguments -func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool, +func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool, withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern { var asString string @@ -103,7 +83,9 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, asString = string(runes) } - cached, found := _patternCache[asString] + // We can uniquely identify the pattern for a given string since + // search mode and caseMode do not change while the program is running + cached, found := patternCache[asString] if found { return cached } @@ -158,7 +140,7 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, cacheable: cacheable, nth: nth, delimiter: delimiter, - cache: _cache, + cache: cache, procFun: make(map[termType]algo.Algo)} ptr.cacheKey = ptr.buildCacheKey() @@ -168,7 +150,7 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, ptr.procFun[termPrefix] = algo.PrefixMatch ptr.procFun[termSuffix] = algo.SuffixMatch - _patternCache[asString] = ptr + patternCache[asString] = ptr return ptr } diff --git a/src/pattern_test.go b/src/pattern_test.go index 5eb5f6d7..9f105f6a 100644 --- a/src/pattern_test.go +++ b/src/pattern_test.go @@ -64,10 +64,15 @@ func TestParseTermsEmpty(t *testing.T) { } } +func buildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool, + withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern { + return BuildPattern(NewChunkCache(), make(map[string]*Pattern), + fuzzy, fuzzyAlgo, extended, caseMode, normalize, forward, + withPos, cacheable, nth, delimiter, runes) +} + func TestExact(t *testing.T) { - defer clearPatternCache() - clearPatternCache() - pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true, + pattern := buildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("'abc")) chars := util.ToChars([]byte("aabbcc abc")) res, pos := algo.ExactMatchNaive( @@ -81,9 +86,7 @@ func TestExact(t *testing.T) { } func TestEqual(t *testing.T) { - defer clearPatternCache() - clearPatternCache() - pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("^AbC$")) + pattern := buildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("^AbC$")) match := func(str string, sidxExpected int, eidxExpected int) { chars := util.ToChars([]byte(str)) @@ -104,19 +107,12 @@ func TestEqual(t *testing.T) { } func TestCaseSensitivity(t *testing.T) { - defer clearPatternCache() - clearPatternCache() - pat1 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("abc")) - clearPatternCache() - pat2 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc")) - clearPatternCache() - pat3 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, false, true, []Range{}, Delimiter{}, []rune("abc")) - clearPatternCache() - pat4 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc")) - clearPatternCache() - pat5 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, false, true, []Range{}, Delimiter{}, []rune("abc")) - clearPatternCache() - pat6 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc")) + pat1 := buildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("abc")) + pat2 := buildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc")) + pat3 := buildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, false, true, []Range{}, Delimiter{}, []rune("abc")) + pat4 := buildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc")) + pat5 := buildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, false, true, []Range{}, Delimiter{}, []rune("abc")) + pat6 := buildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc")) if string(pat1.text) != "abc" || pat1.caseSensitive != false || string(pat2.text) != "Abc" || pat2.caseSensitive != true || @@ -129,7 +125,7 @@ func TestCaseSensitivity(t *testing.T) { } func TestOrigTextAndTransformed(t *testing.T) { - pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("jg")) + pattern := buildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("jg")) tokens := Tokenize("junegunn", Delimiter{}) trans := Transform(tokens, []Range{{1, 1}}) @@ -163,15 +159,13 @@ func TestOrigTextAndTransformed(t *testing.T) { func TestCacheKey(t *testing.T) { test := func(extended bool, patStr string, expected string, cacheable bool) { - clearPatternCache() - pat := BuildPattern(true, algo.FuzzyMatchV2, extended, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune(patStr)) + pat := buildPattern(true, algo.FuzzyMatchV2, extended, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune(patStr)) if pat.CacheKey() != expected { t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey()) } if pat.cacheable != cacheable { t.Errorf("Expected: %t, actual: %t (%s)", cacheable, pat.cacheable, patStr) } - clearPatternCache() } test(false, "foo !bar", "foo !bar", true) test(false, "foo | bar !baz", "foo | bar !baz", true) @@ -187,15 +181,13 @@ func TestCacheKey(t *testing.T) { func TestCacheable(t *testing.T) { test := func(fuzzy bool, str string, expected string, cacheable bool) { - clearPatternCache() - pat := BuildPattern(fuzzy, algo.FuzzyMatchV2, true, CaseSmart, true, true, false, true, []Range{}, Delimiter{}, []rune(str)) + pat := buildPattern(fuzzy, algo.FuzzyMatchV2, true, CaseSmart, true, true, false, true, []Range{}, Delimiter{}, []rune(str)) if pat.CacheKey() != expected { t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey()) } if cacheable != pat.cacheable { t.Errorf("Invalid Pattern.cacheable for \"%s\": %v (expected: %v)", str, pat.cacheable, cacheable) } - clearPatternCache() } test(true, "foo bar", "foo\tbar", true) test(true, "foo 'bar", "foo\tbar", false)