Skip to content

Commit

Permalink
eskip: improve lexer performance (#2755)
Browse files Browse the repository at this point in the history
* eskip: use larger predicate for BenchmarkParsePredicates

Signed-off-by: Alexander Yastrebov <[email protected]>

* eskip: optimize lexer performance

* 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]>

---------

Signed-off-by: Alexander Yastrebov <[email protected]>
  • Loading branch information
AlexanderYastrebov authored Jan 16, 2024
1 parent e44f2b8 commit d468a59
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 162 deletions.
62 changes: 22 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,44 @@ 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
}

if len(rs) == 0 {
return nil, nil
}

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
15 changes: 14 additions & 1 deletion eskip/eskip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
)

func checkItems(t *testing.T, message string, l, lenExpected int, checkItem func(int) bool) bool {
t.Helper()
if l != lenExpected {
t.Error(message, "length", l, lenExpected)
return false
Expand All @@ -25,6 +26,7 @@ func checkItems(t *testing.T, message string, l, lenExpected int, checkItem func
}

func checkFilters(t *testing.T, message string, fs, fsExp []*Filter) bool {
t.Helper()
return checkItems(t, "filters "+message,
len(fs),
len(fsExp),
Expand Down Expand Up @@ -823,7 +825,18 @@ func TestFilterString(t *testing.T) {
}

func BenchmarkParsePredicates(b *testing.B) {
doc := `Foo("bar", "baz")`
doc := `FooBarBazKeyValues(
"https://example.org/foo0", "foobarbaz0",
"https://example.org/foo1", "foobarbaz1",
"https://example.org/foo2", "foobarbaz2",
"https://example.org/foo3", "foobarbaz3",
"https://example.org/foo4", "foobarbaz4",
"https://example.org/foo5", "foobarbaz5",
"https://example.org/foo6", "foobarbaz6",
"https://example.org/foo7", "foobarbaz7",
"https://example.org/foo8", "foobarbaz8",
"https://example.org/foo9", "foobarbaz9")`

_, err := ParsePredicates(doc)
if err != nil {
b.Fatal(err)
Expand Down
Loading

0 comments on commit d468a59

Please sign in to comment.