From 740758a5fa512707155bea83a6faacca8ad991d1 Mon Sep 17 00:00:00 2001 From: greatroar <@> Date: Thu, 8 Oct 2020 11:00:25 +0200 Subject: [PATCH] Optimize filter pattern matching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit By replacing "**" with "", checking for this special path component can be reduced to a length-zero check. name old time/op new time/op delta FilterLines-8 44.7ms ± 5% 44.9ms ± 5% ~ (p=0.631 n=10+10) FilterPatterns/Relative-8 13.6ms ± 4% 13.4ms ± 5% ~ (p=0.165 n=10+10) FilterPatterns/Absolute-8 10.9ms ± 5% 10.7ms ± 4% ~ (p=0.052 n=10+10) FilterPatterns/Wildcard-8 53.7ms ± 5% 50.4ms ± 5% -6.00% (p=0.000 n=10+10) FilterPatterns/ManyNoMatch-8 128ms ± 2% 95ms ± 1% -25.54% (p=0.000 n=10+10) name old alloc/op new alloc/op delta FilterPatterns/Relative-8 3.57MB ± 0% 3.57MB ± 0% ~ (p=1.000 n=9+8) FilterPatterns/Absolute-8 3.57MB ± 0% 3.57MB ± 0% ~ (p=0.903 n=9+8) FilterPatterns/Wildcard-8 19.7MB ± 0% 19.7MB ± 0% -0.00% (p=0.022 n=10+9) FilterPatterns/ManyNoMatch-8 3.57MB ± 0% 3.57MB ± 0% ~ (all equal) name old allocs/op new allocs/op delta FilterPatterns/Relative-8 22.2k ± 0% 22.2k ± 0% ~ (all equal) FilterPatterns/Absolute-8 22.2k ± 0% 22.2k ± 0% ~ (all equal) FilterPatterns/Wildcard-8 88.7k ± 0% 88.7k ± 0% ~ (all equal) FilterPatterns/ManyNoMatch-8 22.2k ± 0% 22.2k ± 0% ~ (all equal) --- internal/filter/filter.go | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/internal/filter/filter.go b/internal/filter/filter.go index 1589819da..50ed6d7fd 100644 --- a/internal/filter/filter.go +++ b/internal/filter/filter.go @@ -12,7 +12,7 @@ import ( var ErrBadString = errors.New("filter.Match: string is empty") type patternPart struct { - pattern string + pattern string // First is "/" for absolute pattern; "" for "**". isSimple bool } @@ -23,25 +23,35 @@ func prepareStr(str string) ([]string, error) { if str == "" { return nil, ErrBadString } - - str = filepath.ToSlash(str) - return strings.Split(str, "/"), nil + return splitPath(str), nil } func preparePattern(pattern string) Pattern { - pattern = filepath.Clean(pattern) - pattern = filepath.ToSlash(pattern) - - parts := strings.Split(pattern, "/") + parts := splitPath(filepath.Clean(pattern)) patterns := make([]patternPart, len(parts)) for i, part := range parts { isSimple := !strings.ContainsAny(part, "\\[]*?") + // Replace "**" with the empty string to get faster comparisons + // (length-check only) in hasDoubleWildcard. + if part == "**" { + part = "" + } patterns[i] = patternPart{part, isSimple} } return patterns } +// Split p into path components. Assuming p has been Cleaned, no component +// will be empty. For absolute paths, the first component is "/". +func splitPath(p string) []string { + parts := strings.Split(filepath.ToSlash(p), "/") + if parts[0] == "" { + parts[0] = "/" + } + return parts +} + // Match returns true if str matches the pattern. When the pattern is // malformed, filepath.ErrBadPattern is returned. The empty pattern matches // everything, when str is the empty string ErrBadString is returned. @@ -93,7 +103,7 @@ func ChildMatch(pattern, str string) (matched bool, err error) { } func childMatch(patterns Pattern, strs []string) (matched bool, err error) { - if patterns[0].pattern != "" { + if patterns[0].pattern != "/" { // relative pattern can always be nested down return true, nil } @@ -116,7 +126,7 @@ func childMatch(patterns Pattern, strs []string) (matched bool, err error) { func hasDoubleWildcard(list Pattern) (ok bool, pos int) { for i, item := range list { - if item.pattern == "**" { + if item.pattern == "" { return true, i } } @@ -159,7 +169,7 @@ func match(patterns Pattern, strs []string) (matched bool, err error) { if len(patterns) <= len(strs) { maxOffset := len(strs) - len(patterns) // special case absolute patterns - if patterns[0].pattern == "" { + if patterns[0].pattern == "/" { maxOffset = 0 } outer: