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