-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
perf: reduce allocations in (*routeRegexp).getURLQuery (#544)
A production server is seeing a significant amount of allocations in (*routeRegexp).getURLQuery Since it is only interested in a single value and only the first value we create a specialized function for that. Comparing a few parameter parsing scenarios: ``` Benchmark_findQueryKey/0-8 7184014 168 ns/op 0 B/op 0 allocs/op Benchmark_findQueryKey/1-8 5307873 227 ns/op 48 B/op 3 allocs/op Benchmark_findQueryKey/2-8 1560836 770 ns/op 483 B/op 10 allocs/op Benchmark_findQueryKey/3-8 1296200 931 ns/op 559 B/op 11 allocs/op Benchmark_findQueryKey/4-8 666502 1769 ns/op 3 B/op 1 allocs/op Benchmark_findQueryKeyGoLib/0-8 1740973 690 ns/op 864 B/op 8 allocs/op Benchmark_findQueryKeyGoLib/1-8 3029618 393 ns/op 432 B/op 4 allocs/op Benchmark_findQueryKeyGoLib/2-8 461427 2511 ns/op 1542 B/op 24 allocs/op Benchmark_findQueryKeyGoLib/3-8 324252 3804 ns/op 1984 B/op 28 allocs/op Benchmark_findQueryKeyGoLib/4-8 69348 14928 ns/op 12716 B/op 130 allocs/op ```
- Loading branch information
Showing
2 changed files
with
132 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package mux | ||
|
||
import ( | ||
"net/url" | ||
"reflect" | ||
"strconv" | ||
"testing" | ||
) | ||
|
||
func Test_findFirstQueryKey(t *testing.T) { | ||
tests := []string{ | ||
"a=1&b=2", | ||
"a=1&a=2&a=banana", | ||
"ascii=%3Ckey%3A+0x90%3E", | ||
"a=1;b=2", | ||
"a=1&a=2;a=banana", | ||
"a==", | ||
"a=%2", | ||
"a=20&%20%3F&=%23+%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09:%2F@$%27%28%29%2A%2C%3B&a=30", | ||
"a=1& ?&=#+%!<>#\"{}|\\^[]`☺\t:/@$'()*,;&a=5", | ||
"a=xxxxxxxxxxxxxxxx&b=YYYYYYYYYYYYYYY&c=ppppppppppppppppppp&f=ttttttttttttttttt&a=uuuuuuuuuuuuu", | ||
} | ||
for _, query := range tests { | ||
t.Run(query, func(t *testing.T) { | ||
// Check against url.ParseQuery, ignoring the error. | ||
all, _ := url.ParseQuery(query) | ||
for key, want := range all { | ||
t.Run(key, func(t *testing.T) { | ||
got, ok := findFirstQueryKey(query, key) | ||
if !ok { | ||
t.Error("Did not get expected key", key) | ||
} | ||
if !reflect.DeepEqual(got, want[0]) { | ||
t.Errorf("findFirstQueryKey(%s,%s) = %v, want %v", query, key, got, want[0]) | ||
} | ||
}) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func Benchmark_findQueryKey(b *testing.B) { | ||
tests := []string{ | ||
"a=1&b=2", | ||
"ascii=%3Ckey%3A+0x90%3E", | ||
"a=20&%20%3F&=%23+%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09:%2F@$%27%28%29%2A%2C%3B&a=30", | ||
"a=xxxxxxxxxxxxxxxx&bbb=YYYYYYYYYYYYYYY&cccc=ppppppppppppppppppp&ddddd=ttttttttttttttttt&a=uuuuuuuuuuuuu", | ||
"a=;b=;c=;d=;e=;f=;g=;h=;i=,j=;k=", | ||
} | ||
for i, query := range tests { | ||
b.Run(strconv.Itoa(i), func(b *testing.B) { | ||
// Check against url.ParseQuery, ignoring the error. | ||
all, _ := url.ParseQuery(query) | ||
b.ReportAllocs() | ||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
for key, _ := range all { | ||
_, _ = findFirstQueryKey(query, key) | ||
} | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func Benchmark_findQueryKeyGoLib(b *testing.B) { | ||
tests := []string{ | ||
"a=1&b=2", | ||
"ascii=%3Ckey%3A+0x90%3E", | ||
"a=20&%20%3F&=%23+%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09:%2F@$%27%28%29%2A%2C%3B&a=30", | ||
"a=xxxxxxxxxxxxxxxx&bbb=YYYYYYYYYYYYYYY&cccc=ppppppppppppppppppp&ddddd=ttttttttttttttttt&a=uuuuuuuuuuuuu", | ||
"a=;b=;c=;d=;e=;f=;g=;h=;i=,j=;k=", | ||
} | ||
for i, query := range tests { | ||
b.Run(strconv.Itoa(i), func(b *testing.B) { | ||
// Check against url.ParseQuery, ignoring the error. | ||
all, _ := url.ParseQuery(query) | ||
var u url.URL | ||
u.RawQuery = query | ||
b.ReportAllocs() | ||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
for key, _ := range all { | ||
v := u.Query()[key] | ||
if len(v) > 0 { | ||
_ = v[0] | ||
} | ||
} | ||
} | ||
}) | ||
} | ||
} |