Skip to content

Commit

Permalink
perf: optionally omit the Route from the request context
Browse files Browse the repository at this point in the history
Optionally save 3 allocations worth 448B per request with no vars.

```
$ go test -benchmem -benchtime 5000000x -bench BenchmarkMuxSimple
goos: linux
goarch: amd64
pkg: github.com/gorilla/mux
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
BenchmarkMuxSimple/default-8                             5000000               349.3 ns/op           496 B/op          4 allocs/op
BenchmarkMuxSimple/omit_route_from_ctx-8                 5000000               157.8 ns/op            48 B/op          1 allocs/op
PASS
ok      github.com/gorilla/mux  2.556s

$ go test -benchmem -benchtime 5000000x -bench BenchmarkMuxSimple
goos: linux
goarch: amd64
pkg: github.com/gorilla/mux
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
BenchmarkMuxSimple/default-8                             5000000               354.7 ns/op           496 B/op          4 allocs/op
BenchmarkMuxSimple/omit_route_from_ctx-8                 5000000               160.8 ns/op            48 B/op          1 allocs/op
PASS
ok      github.com/gorilla/mux  2.602s

$ go test -benchmem -benchtime 5000000x -bench BenchmarkMuxSimple
goos: linux
goarch: amd64
pkg: github.com/gorilla/mux
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
BenchmarkMuxSimple/default-8                             5000000               376.4 ns/op           496 B/op          4 allocs/op
BenchmarkMuxSimple/omit_route_from_ctx-8                 5000000               168.1 ns/op            48 B/op          1 allocs/op
PASS
ok      github.com/gorilla/mux  2.745s
```

```
$ go test -benchmem -benchtime 5000000x -bench BenchmarkPopulateContext
goos: linux
goarch: amd64
pkg: github.com/gorilla/mux
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
BenchmarkPopulateContext/no_populated_vars-8             5000000               381.6 ns/op           496 B/op          4 allocs/op
BenchmarkPopulateContext/empty_var-8                     5000000               913.6 ns/op           928 B/op          9 allocs/op
BenchmarkPopulateContext/populated_vars-8                5000000               914.0 ns/op           912 B/op          8 allocs/op
BenchmarkPopulateContext/omit_route_/static-8            5000000               168.6 ns/op            48 B/op          1 allocs/op
BenchmarkPopulateContext/omit_route_/dynamic-8           5000000               827.4 ns/op           880 B/op          8 allocs/op
PASS
ok      github.com/gorilla/mux  16.049s
```

Signed-off-by: Jakob Ackermann <[email protected]>
  • Loading branch information
das7pad committed Sep 3, 2023
1 parent f3aa4cb commit 1465de4
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 17 deletions.
29 changes: 24 additions & 5 deletions bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,30 @@ func BenchmarkMuxSimple(b *testing.B) {
handler := func(w http.ResponseWriter, r *http.Request) {}
router.HandleFunc("/status", handler)

request, _ := http.NewRequest("GET", "/status", nil)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
router.ServeHTTP(nil, request)
testCases := []struct {
name string
omitRouteFromContext bool
}{
{
name: "default",
omitRouteFromContext: false,
},
{
name: "omit route from ctx",
omitRouteFromContext: true,
},
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
router.OmitRouteFromContext(tc.omitRouteFromContext)

request, _ := http.NewRequest("GET", "/status", nil)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
router.ServeHTTP(nil, request)
}
})
}
}

Expand Down
20 changes: 19 additions & 1 deletion mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ type routeConf struct {
// will not redirect
skipClean bool

// If true, the http.Request context will not contain the Route.
omitRouteFromContext bool

// Manager for the variables from host and path.
regexp routeRegexpGroup

Expand Down Expand Up @@ -190,7 +193,14 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
var handler http.Handler
if r.Match(req, &match) {
handler = match.Handler
req = requestWithRouteAndVars(req, match.Route, match.Vars)
if handler == nil {
// The default handlers do not make use of the context values.
} else if r.omitRouteFromContext {
// Only populate the match vars (if any) into the context.
req = requestWithVars(req, match.Vars)
} else {
req = requestWithRouteAndVars(req, match.Route, match.Vars)
}
}

if handler == nil && match.MatchErr == ErrMethodMismatch {
Expand Down Expand Up @@ -252,6 +262,14 @@ func (r *Router) SkipClean(value bool) *Router {
return r
}

// OmitRouteFromContext defines the behavior of omitting the Route from the
// http.Request context.
// CurrentRoute will yield nil with this option.
func (r *Router) OmitRouteFromContext(value bool) *Router {
r.omitRouteFromContext = value
return r
}

// UseEncodedPath tells the router to match the encoded original path
// to the routes.
// For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to".
Expand Down
41 changes: 30 additions & 11 deletions mux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2914,18 +2914,20 @@ func TestGetVarNames(t *testing.T) {
}

func getPopulateContextTestCases() []struct {
name string
path string
wantVar string
wantStaticRoute bool
wantDynamicRoute bool
name string
path string
omitRouteFromContext bool
wantVar string
wantStaticRoute bool
wantDynamicRoute bool
} {
return []struct {
name string
path string
wantVar string
wantStaticRoute bool
wantDynamicRoute bool
name string
path string
omitRouteFromContext bool
wantVar string
wantStaticRoute bool
wantDynamicRoute bool
}{
{
name: "no populated vars",
Expand All @@ -2938,12 +2940,27 @@ func getPopulateContextTestCases() []struct {
path: "/dynamic/",
wantVar: "",
wantDynamicRoute: true,
}, {
},
{
name: "populated vars",
path: "/dynamic/foo",
wantVar: "foo",
wantDynamicRoute: true,
},
{
name: "omit route /static",
path: "/static",
omitRouteFromContext: true,
wantVar: "",
wantStaticRoute: false,
},
{
name: "omit route /dynamic",
path: "/dynamic/",
omitRouteFromContext: true,
wantVar: "",
wantDynamicRoute: false,
},
}
}

Expand All @@ -2953,6 +2970,7 @@ func TestPopulateContext(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
matched := false
r := NewRouter()
r.OmitRouteFromContext(tc.omitRouteFromContext)
var static *Route
var dynamic *Route
fn := func(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -2996,6 +3014,7 @@ func BenchmarkPopulateContext(b *testing.B) {
b.Run(tc.name, func(b *testing.B) {
matched := false
r := NewRouter()
r.OmitRouteFromContext(tc.omitRouteFromContext)
fn := func(w http.ResponseWriter, r *http.Request) {
matched = true
w.WriteHeader(http.StatusNoContent)
Expand Down

0 comments on commit 1465de4

Please sign in to comment.