Optimize filter pattern matching

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)
This commit is contained in:
greatroar 2020-10-08 11:00:25 +02:00 committed by MichaelEischer
parent 8388e67c4b
commit 740758a5fa

View file

@ -12,7 +12,7 @@ import (
var ErrBadString = errors.New("filter.Match: string is empty") var ErrBadString = errors.New("filter.Match: string is empty")
type patternPart struct { type patternPart struct {
pattern string pattern string // First is "/" for absolute pattern; "" for "**".
isSimple bool isSimple bool
} }
@ -23,25 +23,35 @@ func prepareStr(str string) ([]string, error) {
if str == "" { if str == "" {
return nil, ErrBadString return nil, ErrBadString
} }
return splitPath(str), nil
str = filepath.ToSlash(str)
return strings.Split(str, "/"), nil
} }
func preparePattern(pattern string) Pattern { func preparePattern(pattern string) Pattern {
pattern = filepath.Clean(pattern) parts := splitPath(filepath.Clean(pattern))
pattern = filepath.ToSlash(pattern)
parts := strings.Split(pattern, "/")
patterns := make([]patternPart, len(parts)) patterns := make([]patternPart, len(parts))
for i, part := range parts { for i, part := range parts {
isSimple := !strings.ContainsAny(part, "\\[]*?") 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} patterns[i] = patternPart{part, isSimple}
} }
return patterns 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 // Match returns true if str matches the pattern. When the pattern is
// malformed, filepath.ErrBadPattern is returned. The empty pattern matches // malformed, filepath.ErrBadPattern is returned. The empty pattern matches
// everything, when str is the empty string ErrBadString is returned. // 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) { 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 // relative pattern can always be nested down
return true, nil 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) { func hasDoubleWildcard(list Pattern) (ok bool, pos int) {
for i, item := range list { for i, item := range list {
if item.pattern == "**" { if item.pattern == "" {
return true, i return true, i
} }
} }
@ -159,7 +169,7 @@ func match(patterns Pattern, strs []string) (matched bool, err error) {
if len(patterns) <= len(strs) { if len(patterns) <= len(strs) {
maxOffset := len(strs) - len(patterns) maxOffset := len(strs) - len(patterns)
// special case absolute patterns // special case absolute patterns
if patterns[0].pattern == "" { if patterns[0].pattern == "/" {
maxOffset = 0 maxOffset = 0
} }
outer: outer: