mirror of https://github.com/gofiber/fiber.git
v3: Improve performance of helper functions (#3086)
* Improve performance of helper functions * Fix issue with PR comments from forkspull/3087/head
parent
8c3f81e2b7
commit
486304d050
|
@ -65,7 +65,8 @@ jobs:
|
|||
# Do not save the data (This allows comparing benchmarks)
|
||||
save-data-file: false
|
||||
fail-on-alert: true
|
||||
comment-on-alert: true
|
||||
# Comment on the PR if the branch is not a fork
|
||||
comment-on-alert: ${{ github.event.pull_request.head.repo.fork == false }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
summary-always: true
|
||||
alert-threshold: "150%"
|
||||
|
|
2
Makefile
2
Makefile
|
@ -29,7 +29,7 @@ format:
|
|||
|
||||
## format: 🎨 Find markdown format issues (Requires markdownlint-cli)
|
||||
.PHONY: markdown
|
||||
format:
|
||||
markdown:
|
||||
markdownlint-cli2 "**/*.md" "#vendor"
|
||||
|
||||
## lint: 🚨 Run lint checks
|
||||
|
|
61
helpers.go
61
helpers.go
|
@ -298,7 +298,7 @@ func paramsMatch(specParamStr headerParams, offerParams string) bool {
|
|||
for specParam, specVal := range specParamStr {
|
||||
foundParam := false
|
||||
fasthttp.VisitHeaderParams(utils.UnsafeBytes(offerParams), func(key, value []byte) bool {
|
||||
if utils.EqualFold(specParam, string(key)) {
|
||||
if utils.EqualFold(specParam, utils.UnsafeString(key)) {
|
||||
foundParam = true
|
||||
allSpecParamsMatch = utils.EqualFold(specVal, value)
|
||||
return false
|
||||
|
@ -423,29 +423,31 @@ func getOffer(header []byte, isAccepted func(spec, offer string, specParams head
|
|||
forEachMediaRange(header, func(accept []byte) {
|
||||
order++
|
||||
spec, quality := accept, 1.0
|
||||
|
||||
var params headerParams
|
||||
|
||||
if i := bytes.IndexByte(accept, ';'); i != -1 {
|
||||
spec = accept[:i]
|
||||
|
||||
// The vast majority of requests will have only the q parameter with
|
||||
// no whitespace. Check this first to see if we can skip
|
||||
// the more involved parsing.
|
||||
if bytes.HasPrefix(accept[i:], []byte(";q=")) && bytes.IndexByte(accept[i+3:], ';') == -1 {
|
||||
if q, err := fasthttp.ParseUfloat(bytes.TrimRight(accept[i+3:], " ")); err == nil {
|
||||
// Optimized quality parsing
|
||||
qIndex := i + 3
|
||||
if bytes.HasPrefix(accept[i:], []byte(";q=")) && bytes.IndexByte(accept[qIndex:], ';') == -1 {
|
||||
if q, err := fasthttp.ParseUfloat(accept[qIndex:]); err == nil {
|
||||
quality = q
|
||||
}
|
||||
} else {
|
||||
params, _ = headerParamPool.Get().(headerParams) //nolint:errcheck // only contains headerParams
|
||||
for k := range params {
|
||||
delete(params, k)
|
||||
}
|
||||
fasthttp.VisitHeaderParams(accept[i:], func(key, value []byte) bool {
|
||||
if string(key) == "q" {
|
||||
if len(key) == 1 && key[0] == 'q' {
|
||||
if q, err := fasthttp.ParseUfloat(value); err == nil {
|
||||
quality = q
|
||||
}
|
||||
return false
|
||||
}
|
||||
params[utils.UnsafeString(utils.ToLowerBytes(key))] = value
|
||||
lowerKey := utils.UnsafeString(utils.ToLowerBytes(key))
|
||||
params[lowerKey] = value
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
@ -457,13 +459,16 @@ func getOffer(header []byte, isAccepted func(spec, offer string, specParams head
|
|||
}
|
||||
}
|
||||
|
||||
spec = bytes.TrimRight(spec, " ")
|
||||
spec = bytes.TrimSpace(spec)
|
||||
|
||||
// Get specificity
|
||||
// Determine specificity
|
||||
var specificity int
|
||||
|
||||
// check for wildcard this could be a mime */* or a wildcard character *
|
||||
switch {
|
||||
case string(spec) == "*/*" || string(spec) == "*":
|
||||
case len(spec) == 1 && spec[0] == '*':
|
||||
specificity = 1
|
||||
case bytes.Equal(spec, []byte("*/*")):
|
||||
specificity = 1
|
||||
case bytes.HasSuffix(spec, []byte("/*")):
|
||||
specificity = 2
|
||||
|
@ -474,7 +479,13 @@ func getOffer(header []byte, isAccepted func(spec, offer string, specParams head
|
|||
}
|
||||
|
||||
// Add to accepted types
|
||||
acceptedTypes = append(acceptedTypes, acceptedType{spec: utils.UnsafeString(spec), quality: quality, specificity: specificity, order: order, params: params})
|
||||
acceptedTypes = append(acceptedTypes, acceptedType{
|
||||
spec: utils.UnsafeString(spec),
|
||||
quality: quality,
|
||||
specificity: specificity,
|
||||
order: order,
|
||||
params: params,
|
||||
})
|
||||
})
|
||||
|
||||
if len(acceptedTypes) > 1 {
|
||||
|
@ -483,30 +494,24 @@ func getOffer(header []byte, isAccepted func(spec, offer string, specParams head
|
|||
}
|
||||
|
||||
// Find the first offer that matches the accepted types
|
||||
ret := ""
|
||||
done := false
|
||||
for _, acceptedType := range acceptedTypes {
|
||||
if !done {
|
||||
for _, offer := range offers {
|
||||
if offer == "" {
|
||||
continue
|
||||
}
|
||||
if isAccepted(acceptedType.spec, offer, acceptedType.params) {
|
||||
ret = offer
|
||||
done = true
|
||||
break
|
||||
for _, offer := range offers {
|
||||
if offer == "" {
|
||||
continue
|
||||
}
|
||||
if isAccepted(acceptedType.spec, offer, acceptedType.params) {
|
||||
if acceptedType.params != nil {
|
||||
headerParamPool.Put(acceptedType.params)
|
||||
}
|
||||
return offer
|
||||
}
|
||||
}
|
||||
if acceptedType.params != nil {
|
||||
for p := range acceptedType.params {
|
||||
delete(acceptedType.params, p)
|
||||
}
|
||||
headerParamPool.Put(acceptedType.params)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
return ""
|
||||
}
|
||||
|
||||
// sortAcceptedTypes sorts accepted types by quality and specificity, preserving order of equal elements
|
||||
|
|
|
@ -138,6 +138,8 @@ func Benchmark_Utils_GetOffer(b *testing.B) {
|
|||
},
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for _, tc := range testCases {
|
||||
accept := []byte(tc.accept)
|
||||
b.Run(tc.description, func(b *testing.B) {
|
||||
|
@ -205,6 +207,8 @@ func Benchmark_Utils_ParamsMatch(b *testing.B) {
|
|||
"appLe": []byte("orange"),
|
||||
"param": []byte("foo"),
|
||||
}
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
match = paramsMatch(specParams, `;param=foo; apple=orange`)
|
||||
}
|
||||
|
@ -317,6 +321,8 @@ func Benchmark_Utils_GetSplicedStrList(b *testing.B) {
|
|||
destination := make([]string, 5)
|
||||
result := destination
|
||||
const input = `deflate, gzip,br,brotli,zstd`
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
result = getSplicedStrList(input, destination)
|
||||
}
|
||||
|
@ -359,6 +365,8 @@ func Test_Utils_SortAcceptedTypes(t *testing.T) {
|
|||
// go test -v -run=^$ -bench=Benchmark_Utils_SortAcceptedTypes_Sorted -benchmem -count=4
|
||||
func Benchmark_Utils_SortAcceptedTypes_Sorted(b *testing.B) {
|
||||
acceptedTypes := make([]acceptedType, 3)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
acceptedTypes[0] = acceptedType{spec: "text/html", quality: 1, specificity: 1, order: 0}
|
||||
acceptedTypes[1] = acceptedType{spec: "text/*", quality: 0.5, specificity: 1, order: 1}
|
||||
|
@ -373,6 +381,8 @@ func Benchmark_Utils_SortAcceptedTypes_Sorted(b *testing.B) {
|
|||
// go test -v -run=^$ -bench=Benchmark_Utils_SortAcceptedTypes_Unsorted -benchmem -count=4
|
||||
func Benchmark_Utils_SortAcceptedTypes_Unsorted(b *testing.B) {
|
||||
acceptedTypes := make([]acceptedType, 11)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
acceptedTypes[0] = acceptedType{spec: "text/html", quality: 1, specificity: 3, order: 0}
|
||||
acceptedTypes[1] = acceptedType{spec: "text/*", quality: 0.5, specificity: 2, order: 1}
|
||||
|
@ -452,9 +462,10 @@ func Test_Utils_getGroupPath(t *testing.T) {
|
|||
}
|
||||
|
||||
// go test -v -run=^$ -bench=Benchmark_Utils_ -benchmem -count=3
|
||||
|
||||
func Benchmark_Utils_getGroupPath(b *testing.B) {
|
||||
var res string
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
_ = getGroupPath("/v1/long/path/john/doe", "/why/this/name/is/so/awesome")
|
||||
_ = getGroupPath("/v1", "/")
|
||||
|
@ -467,7 +478,8 @@ func Benchmark_Utils_getGroupPath(b *testing.B) {
|
|||
func Benchmark_Utils_Unescape(b *testing.B) {
|
||||
unescaped := ""
|
||||
dst := make([]byte, 0)
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
source := "/cr%C3%A9er"
|
||||
pathBytes := utils.UnsafeBytes(source)
|
||||
|
@ -529,6 +541,8 @@ func Test_Utils_IsNoCache(t *testing.T) {
|
|||
// go test -v -run=^$ -bench=Benchmark_Utils_IsNoCache -benchmem -count=4
|
||||
func Benchmark_Utils_IsNoCache(b *testing.B) {
|
||||
var ok bool
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = isNoCache("public")
|
||||
_ = isNoCache("no-cache")
|
||||
|
@ -544,7 +558,10 @@ func Benchmark_Utils_IsNoCache(b *testing.B) {
|
|||
func Benchmark_SlashRecognition(b *testing.B) {
|
||||
search := "wtf/1234"
|
||||
var result bool
|
||||
|
||||
b.Run("indexBytes", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
result = false
|
||||
for i := 0; i < b.N; i++ {
|
||||
if strings.IndexByte(search, slashDelimiter) != -1 {
|
||||
|
@ -554,6 +571,8 @@ func Benchmark_SlashRecognition(b *testing.B) {
|
|||
require.True(b, result)
|
||||
})
|
||||
b.Run("forEach", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
result = false
|
||||
c := int32(slashDelimiter)
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
@ -567,6 +586,8 @@ func Benchmark_SlashRecognition(b *testing.B) {
|
|||
require.True(b, result)
|
||||
})
|
||||
b.Run("IndexRune", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
result = false
|
||||
c := int32(slashDelimiter)
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
|
Loading…
Reference in New Issue