Skip to content

Commit

Permalink
eskip: optimize lexer performance
Browse files Browse the repository at this point in the history
* do not use fmt.Sprintf in ParseFilters and ParsePredicates
* avoid allocations for fixed tokens
* optimize scanWhile loop
* add scanEscaped fast path
* optimize scanRegexp and scanEscaped using strings.Builder
* optimize scanWhitespace
* optimize selectScanner using switch

```
goos: linux
goarch: amd64
pkg: github.com/zalando/skipper/eskip
cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz
                  │   HEAD~1    │                HEAD                 │
                  │   sec/op    │   sec/op     vs base                │
ParsePredicates-8   29.88µ ± 3%   11.33µ ± 2%  -62.09% (p=0.000 n=10)

                  │    HEAD~1    │                 HEAD                 │
                  │     B/op     │     B/op      vs base                │
ParsePredicates-8   4.863Ki ± 0%   2.008Ki ± 0%  -58.71% (p=0.000 n=10)

                  │   HEAD~1    │                HEAD                │
                  │  allocs/op  │ allocs/op   vs base                │
ParsePredicates-8   198.00 ± 0%   33.00 ± 0%  -83.33% (p=0.000 n=10)
```

Signed-off-by: Alexander Yastrebov <[email protected]>
  • Loading branch information
AlexanderYastrebov committed Jan 15, 2024
1 parent 892c789 commit 32540a3
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 161 deletions.
58 changes: 18 additions & 40 deletions eskip/eskip.go
Original file line number Diff line number Diff line change
Expand Up @@ -616,25 +616,6 @@ func parse(code string) ([]*parsedRoute, error) {
return lp.lexer.routes, lp.lexer.err
}

func partialRouteToRoute(format, p string) string {
p = strings.TrimSpace(p)
if p == "" {
return ""
}

return fmt.Sprintf(format, p)
}

// hacks a filter expression into a route expression for parsing.
func filtersToRoute(f string) string {
return partialRouteToRoute("* -> %s -> <shunt>", f)
}

// hacks a predicate expression into a route expression for parsing.
func predicatesToRoute(p string) string {
return partialRouteToRoute("%s -> <shunt>", p)
}

// Parses a route expression or a routing document to a set of route definitions.
func Parse(code string) ([]*Route, error) {
parsedRoutes, err := parse(code)
Expand Down Expand Up @@ -685,43 +666,40 @@ func MustParseFilters(s string) []*Filter {
return p
}

func partialParse(f string, partialToRoute func(string) string) (*parsedRoute, error) {
rs, err := parse(partialToRoute(f))
if err != nil {
return nil, err
}

if len(rs) == 0 {
// Parses a filter chain into a list of parsed filter definitions.
func ParseFilters(f string) ([]*Filter, error) {
f = strings.TrimSpace(f)
if f == "" {
return nil, nil
}

return rs[0], nil
}

// Parses a filter chain into a list of parsed filter definitions.
func ParseFilters(f string) ([]*Filter, error) {
r, err := partialParse(f, filtersToRoute)
if r == nil || err != nil {
rs, err := parse("* -> " + f + " -> <shunt>")
if err != nil {
return nil, err
}

return r.filters, nil
return rs[0].filters, nil
}

// ParsePredicates parses a set of predicates (combined by '&&') into
// a list of parsed predicate definitions.
func ParsePredicates(p string) ([]*Predicate, error) {
r, err := partialParse(p, predicatesToRoute)
if r == nil || err != nil {
p = strings.TrimSpace(p)
if p == "" {
return nil, nil
}

rs, err := parse(p + " -> <shunt>")
if err != nil {
return nil, err
}

var ps []*Predicate
for i := range r.matchers {
if r.matchers[i].name != "*" {
for _, matcher := range rs[0].matchers {
if matcher.name != "*" {
ps = append(ps, &Predicate{
Name: r.matchers[i].name,
Args: r.matchers[i].args,
Name: matcher.name,
Args: matcher.args,
})
}
}
Expand Down
Loading

0 comments on commit 32540a3

Please sign in to comment.