filter: special case patterns without globbing characters

In case a part of a path is a simple string, we can just check for
equality without complex parsing in filepath.Match.

name                          old time/op    new time/op    delta
FilterLines-4                   34.8ms ±17%    41.2ms ±23%  +18.36%  (p=0.000 n=10+10)
FilterPatterns/Relative-4       21.7ms ± 6%    12.1ms ±23%  -44.46%  (p=0.000 n=10+10)
FilterPatterns/Absolute-4       10.0ms ± 5%     9.1ms ±11%   -9.80%  (p=0.006 n=10+9)
FilterPatterns/Wildcard-4       47.0ms ± 7%    42.2ms ± 5%  -10.19%  (p=0.000 n=9+10)
FilterPatterns/ManyNoMatch-4     190ms ± 1%     131ms ±20%  -31.47%  (p=0.000 n=8+10)

name                          old alloc/op   new alloc/op   delta
FilterPatterns/Relative-4       3.57MB ± 0%    3.57MB ± 0%     ~     (p=0.870 n=9+9)
FilterPatterns/Absolute-4       3.57MB ± 0%    3.57MB ± 0%     ~     (p=0.145 n=10+10)
FilterPatterns/Wildcard-4       14.3MB ± 0%    19.7MB ± 0%  +37.91%  (p=0.000 n=10+10)
FilterPatterns/ManyNoMatch-4    3.57MB ± 0%    3.57MB ± 0%     ~     (p=0.421 n=10+9)

name                          old allocs/op  new allocs/op  delta
FilterPatterns/Relative-4        22.2k ± 0%     22.2k ± 0%     ~     (all equal)
FilterPatterns/Absolute-4        22.2k ± 0%     22.2k ± 0%     ~     (all equal)
FilterPatterns/Wildcard-4        88.7k ± 0%     88.7k ± 0%     ~     (all equal)
FilterPatterns/ManyNoMatch-4     22.2k ± 0%     22.2k ± 0%     ~     (all equal)
This commit is contained in:
Michael Eischer 2020-10-07 20:55:43 +02:00
parent 54a124de3b
commit 0acc3c5923

View file

@ -11,8 +11,13 @@ import (
// second argument. // second argument.
var ErrBadString = errors.New("filter.Match: string is empty") var ErrBadString = errors.New("filter.Match: string is empty")
type patternPart struct {
pattern string
isSimple bool
}
// Pattern represents a preparsed filter pattern // Pattern represents a preparsed filter pattern
type Pattern []string type Pattern []patternPart
func prepareStr(str string) ([]string, error) { func prepareStr(str string) ([]string, error) {
if str == "" { if str == "" {
@ -35,7 +40,14 @@ func preparePattern(pattern string) Pattern {
pattern = strings.Replace(pattern, string(filepath.Separator), "/", -1) pattern = strings.Replace(pattern, string(filepath.Separator), "/", -1)
} }
return strings.Split(pattern, "/") parts := strings.Split(pattern, "/")
patterns := make([]patternPart, len(parts))
for i, part := range parts {
isSimple := !strings.ContainsAny(part, "\\[]*?")
patterns[i] = patternPart{part, isSimple}
}
return patterns
} }
// Match returns true if str matches the pattern. When the pattern is // Match returns true if str matches the pattern. When the pattern is
@ -89,7 +101,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] != "" { if patterns[0].pattern != "" {
// relative pattern can always be nested down // relative pattern can always be nested down
return true, nil return true, nil
} }
@ -112,7 +124,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 == "**" { if item.pattern == "**" {
return true, i return true, i
} }
} }
@ -131,7 +143,7 @@ func match(patterns Pattern, strs []string) (matched bool, err error) {
newPat := newPat[:pos+i] newPat := newPat[:pos+i]
// in the first iteration the wildcard expands to nothing // in the first iteration the wildcard expands to nothing
if i > 0 { if i > 0 {
newPat[pos+i-1] = "*" newPat[pos+i-1] = patternPart{"*", false}
} }
newPat = append(newPat, patterns[pos+1:]...) newPat = append(newPat, patterns[pos+1:]...)
@ -155,16 +167,21 @@ 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] == "" { if patterns[0].pattern == "" {
maxOffset = 0 maxOffset = 0
} }
outer: outer:
for offset := maxOffset; offset >= 0; offset-- { for offset := maxOffset; offset >= 0; offset-- {
for i := len(patterns) - 1; i >= 0; i-- { for i := len(patterns) - 1; i >= 0; i-- {
ok, err := filepath.Match(patterns[i], strs[offset+i]) var ok bool
if err != nil { if patterns[i].isSimple {
return false, errors.Wrap(err, "Match") ok = patterns[i].pattern == strs[offset+i]
} else {
ok, err = filepath.Match(patterns[i].pattern, strs[offset+i])
if err != nil {
return false, errors.Wrap(err, "Match")
}
} }
if !ok { if !ok {