♻️ Refactor: reduce the memory usage of RoutePatternMatch (#3335)

* ♻️ Refactor: improve RoutePatternMatch by adding RemoveEscapeCharBytes

```
goos: linux
goarch: amd64
pkg: github.com/gofiber/fiber/v3
cpu: AMD EPYC 7763 64-Core Processor
                                                                              │ route_pattern_match_old.txt │    route_pattern_match_new.txt     │
                                                                              │           sec/op            │   sec/op     vs base               │
_RoutePatternMatch//api/v1/const_|_match_|_/api/v1/const-4                                      263.4n ± 2%   249.0n ± 4%  -5.47% (p=0.001 n=10)
_RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1-4                                        258.7n ± 4%   244.7n ± 2%  -5.43% (p=0.000 n=10)
_RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1/-4                                       254.6n ± 4%   246.3n ± 2%  -3.26% (p=0.000 n=10)
_RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1/something-4                              265.1n ± 4%   255.6n ± 3%  -3.60% (p=0.001 n=10)
_RoutePatternMatch//api/:param/fixedEnd_|_match_|_/api/abc/fixedEnd-4                           775.9n ± 3%   775.6n ± 2%       ~ (p=0.424 n=10)
_RoutePatternMatch//api/:param/fixedEnd_|_not_match_|_/api/abc/def/fixedEnd-4                   796.7n ± 3%   767.1n ± 2%  -3.72% (p=0.001 n=10)
_RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity-4                                  916.2n ± 1%   904.8n ± 3%       ~ (p=0.052 n=10)
_RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity/-4                                 913.8n ± 4%   909.1n ± 3%       ~ (p=0.393 n=10)
_RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity/1-4                                915.0n ± 3%   907.2n ± 2%       ~ (p=0.165 n=10)
_RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v-4                                      917.5n ± 2%   876.7n ± 2%  -4.46% (p=0.000 n=10)
_RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v2-4                                     918.5n ± 2%   886.8n ± 2%  -3.45% (p=0.000 n=10)
_RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v1/-4                                    935.6n ± 2%   901.9n ± 2%  -3.60% (p=0.000 n=10)
geomean                                                                                         588.3n        570.7n       -2.99%

                                                                              │ route_pattern_match_old.txt │     route_pattern_match_new.txt      │
                                                                              │            B/op             │    B/op     vs base                  │
_RoutePatternMatch//api/v1/const_|_match_|_/api/v1/const-4                                       168.0 ± 0%   152.0 ± 0%   -9.52% (p=0.000 n=10)
_RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1-4                                         160.0 ± 0%   144.0 ± 0%  -10.00% (p=0.000 n=10)
_RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1/-4                                        160.0 ± 0%   144.0 ± 0%  -10.00% (p=0.000 n=10)
_RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1/something-4                               176.0 ± 0%   160.0 ± 0%   -9.09% (p=0.000 n=10)
_RoutePatternMatch//api/:param/fixedEnd_|_match_|_/api/abc/fixedEnd-4                            440.0 ± 0%   440.0 ± 0%        ~ (p=1.000 n=10) ¹
_RoutePatternMatch//api/:param/fixedEnd_|_not_match_|_/api/abc/def/fixedEnd-4                    464.0 ± 0%   440.0 ± 0%   -5.17% (p=0.000 n=10)
_RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity-4                                   536.0 ± 0%   536.0 ± 0%        ~ (p=1.000 n=10) ¹
_RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity/-4                                  536.0 ± 0%   536.0 ± 0%        ~ (p=1.000 n=10) ¹
_RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity/1-4                                 536.0 ± 0%   536.0 ± 0%        ~ (p=1.000 n=10) ¹
_RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v-4                                       544.0 ± 0%   528.0 ± 0%   -2.94% (p=0.000 n=10)
_RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v2-4                                      544.0 ± 0%   528.0 ± 0%   -2.94% (p=0.000 n=10)
_RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v1/-4                                     544.0 ± 0%   528.0 ± 0%   -2.94% (p=0.000 n=10)
geomean                                                                                          353.7        337.9        -4.47%
¹ all samples are equal

                                                                              │ route_pattern_match_old.txt │     route_pattern_match_new.txt      │
                                                                              │          allocs/op          │ allocs/op   vs base                  │
_RoutePatternMatch//api/v1/const_|_match_|_/api/v1/const-4                                       6.000 ± 0%   5.000 ± 0%  -16.67% (p=0.000 n=10)
_RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1-4                                         6.000 ± 0%   5.000 ± 0%  -16.67% (p=0.000 n=10)
_RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1/-4                                        6.000 ± 0%   5.000 ± 0%  -16.67% (p=0.000 n=10)
_RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1/something-4                               6.000 ± 0%   5.000 ± 0%  -16.67% (p=0.000 n=10)
_RoutePatternMatch//api/:param/fixedEnd_|_match_|_/api/abc/fixedEnd-4                            13.00 ± 0%   13.00 ± 0%        ~ (p=1.000 n=10) ¹
_RoutePatternMatch//api/:param/fixedEnd_|_not_match_|_/api/abc/def/fixedEnd-4                    14.00 ± 0%   13.00 ± 0%   -7.14% (p=0.000 n=10)
_RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity-4                                   14.00 ± 0%   14.00 ± 0%        ~ (p=1.000 n=10) ¹
_RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity/-4                                  14.00 ± 0%   14.00 ± 0%        ~ (p=1.000 n=10) ¹
_RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity/1-4                                 14.00 ± 0%   14.00 ± 0%        ~ (p=1.000 n=10) ¹
_RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v-4                                       15.00 ± 0%   14.00 ± 0%   -6.67% (p=0.000 n=10)
_RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v2-4                                      15.00 ± 0%   14.00 ± 0%   -6.67% (p=0.000 n=10)
_RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v1/-4                                     15.00 ± 0%   14.00 ± 0%   -6.67% (p=0.000 n=10)
geomean                                                                                          10.67        9.811        -8.08%
¹ all samples are equal
```

* ♻️ Refactor: returned type of analyseParameterPart and analyseConstantPart

```
goos: linux
goarch: amd64
pkg: github.com/gofiber/fiber/v3
cpu: AMD EPYC 7763 64-Core Processor
                                                                              │ route_pattern_match_old.txt │    route_pattern_match_new3.txt    │
                                                                              │           sec/op            │   sec/op     vs base               │
_RoutePatternMatch//api/v1/const_|_match_|_/api/v1/const-4                                      264.3n ± 2%   253.8n ± 2%  -3.95% (p=0.001 n=10)
_RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1-4                                        258.5n ± 1%   247.6n ± 2%  -4.24% (p=0.000 n=10)
_RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1/-4                                       260.8n ± 3%   249.7n ± 4%  -4.26% (p=0.003 n=10)
_RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1/something-4                              265.4n ± 2%   256.1n ± 2%  -3.49% (p=0.000 n=10)
_RoutePatternMatch//api/:param/fixedEnd_|_match_|_/api/abc/fixedEnd-4                           783.8n ± 2%   777.5n ± 3%       ~ (p=0.218 n=10)
_RoutePatternMatch//api/:param/fixedEnd_|_not_match_|_/api/abc/def/fixedEnd-4                   797.8n ± 1%   773.6n ± 3%  -3.03% (p=0.001 n=10)
_RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity-4                                  920.3n ± 2%   926.0n ± 3%       ~ (p=0.896 n=10)
_RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity/-4                                 920.4n ± 4%   908.2n ± 2%       ~ (p=0.063 n=10)
_RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity/1-4                                927.9n ± 2%   919.0n ± 3%       ~ (p=0.579 n=10)
_RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v-4                                      920.4n ± 3%   889.5n ± 3%  -3.36% (p=0.007 n=10)
_RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v2-4                                     916.9n ± 2%   891.9n ± 2%  -2.73% (p=0.000 n=10)
_RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v1/-4                                    938.8n ± 5%   891.2n ± 2%  -5.07% (p=0.000 n=10)
geomean                                                                                         591.7n        575.5n       -2.73%

                                                                              │ route_pattern_match_old.txt │     route_pattern_match_new3.txt     │
                                                                              │            B/op             │    B/op     vs base                  │
_RoutePatternMatch//api/v1/const_|_match_|_/api/v1/const-4                                       168.0 ± 0%   152.0 ± 0%   -9.52% (p=0.000 n=10)
_RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1-4                                         160.0 ± 0%   144.0 ± 0%  -10.00% (p=0.000 n=10)
_RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1/-4                                        160.0 ± 0%   144.0 ± 0%  -10.00% (p=0.000 n=10)
_RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1/something-4                               176.0 ± 0%   160.0 ± 0%   -9.09% (p=0.000 n=10)
_RoutePatternMatch//api/:param/fixedEnd_|_match_|_/api/abc/fixedEnd-4                            440.0 ± 0%   440.0 ± 0%        ~ (p=1.000 n=10) ¹
_RoutePatternMatch//api/:param/fixedEnd_|_not_match_|_/api/abc/def/fixedEnd-4                    464.0 ± 0%   440.0 ± 0%   -5.17% (p=0.000 n=10)
_RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity-4                                   536.0 ± 0%   536.0 ± 0%        ~ (p=1.000 n=10) ¹
_RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity/-4                                  536.0 ± 0%   536.0 ± 0%        ~ (p=1.000 n=10) ¹
_RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity/1-4                                 536.0 ± 0%   536.0 ± 0%        ~ (p=1.000 n=10) ¹
_RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v-4                                       544.0 ± 0%   528.0 ± 0%   -2.94% (p=0.000 n=10)
_RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v2-4                                      544.0 ± 0%   528.0 ± 0%   -2.94% (p=0.000 n=10)
_RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v1/-4                                     544.0 ± 0%   528.0 ± 0%   -2.94% (p=0.000 n=10)
geomean                                                                                          353.7        337.9        -4.47%
¹ all samples are equal

                                                                              │ route_pattern_match_old.txt │     route_pattern_match_new3.txt     │
                                                                              │          allocs/op          │ allocs/op   vs base                  │
_RoutePatternMatch//api/v1/const_|_match_|_/api/v1/const-4                                       6.000 ± 0%   5.000 ± 0%  -16.67% (p=0.000 n=10)
_RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1-4                                         6.000 ± 0%   5.000 ± 0%  -16.67% (p=0.000 n=10)
_RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1/-4                                        6.000 ± 0%   5.000 ± 0%  -16.67% (p=0.000 n=10)
_RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1/something-4                               6.000 ± 0%   5.000 ± 0%  -16.67% (p=0.000 n=10)
_RoutePatternMatch//api/:param/fixedEnd_|_match_|_/api/abc/fixedEnd-4                            13.00 ± 0%   13.00 ± 0%        ~ (p=1.000 n=10) ¹
_RoutePatternMatch//api/:param/fixedEnd_|_not_match_|_/api/abc/def/fixedEnd-4                    14.00 ± 0%   13.00 ± 0%   -7.14% (p=0.000 n=10)
_RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity-4                                   14.00 ± 0%   14.00 ± 0%        ~ (p=1.000 n=10) ¹
_RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity/-4                                  14.00 ± 0%   14.00 ± 0%        ~ (p=1.000 n=10) ¹
_RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity/1-4                                 14.00 ± 0%   14.00 ± 0%        ~ (p=1.000 n=10) ¹
_RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v-4                                       15.00 ± 0%   14.00 ± 0%   -6.67% (p=0.000 n=10)
_RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v2-4                                      15.00 ± 0%   14.00 ± 0%   -6.67% (p=0.000 n=10)
_RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v1/-4                                     15.00 ± 0%   14.00 ± 0%   -6.67% (p=0.000 n=10)
geomean                                                                                          10.67        9.811        -8.08%
¹ all samples are equal
```

---------

Co-authored-by: RW <rene@gofiber.io>
This commit is contained in:
Kashiwa 2025-03-03 15:31:20 +08:00 committed by GitHub
parent 6afba957f1
commit 9e6f4fd408
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

60
path.go
View File

@ -152,11 +152,11 @@ func RoutePatternMatch(path, pattern string, cfg ...Config) bool {
pattern = "/" + pattern
}
patternPretty := pattern
patternPretty := []byte(pattern)
// Case-sensitive routing, all to lowercase
if !config.CaseSensitive {
patternPretty = utils.ToLower(patternPretty)
patternPretty = utils.ToLowerBytes(patternPretty)
path = utils.ToLower(path)
}
// Strict routing, remove trailing slashes
@ -164,12 +164,12 @@ func RoutePatternMatch(path, pattern string, cfg ...Config) bool {
patternPretty = utils.TrimRight(patternPretty, '/')
}
parser := parseRoute(patternPretty)
parser := parseRoute(string(patternPretty))
if patternPretty == "/" && path == "/" {
if string(patternPretty) == "/" && path == "/" {
return true
// '*' wildcard matches any path
} else if patternPretty == "/*" {
} else if string(patternPretty) == "/*" {
return true
}
@ -180,35 +180,28 @@ func RoutePatternMatch(path, pattern string, cfg ...Config) bool {
}
}
// Check for a simple match
patternPretty = RemoveEscapeChar(patternPretty)
if len(patternPretty) == len(path) && patternPretty == path {
return true
}
// No match
return false
patternPretty = RemoveEscapeCharBytes(patternPretty)
return string(patternPretty) == path
}
// parseRoute analyzes the route and divides it into segments for constant areas and parameters,
// this information is needed later when assigning the requests to the declared routes
func parseRoute(pattern string, customConstraints ...CustomConstraint) routeParser {
parser := routeParser{}
part := ""
var n int
var seg *routeSegment
for len(pattern) > 0 {
nextParamPosition := findNextParamPosition(pattern)
// handle the parameter part
if nextParamPosition == 0 {
processedPart, seg := parser.analyseParameterPart(pattern, customConstraints...)
parser.params, parser.segs, part = append(parser.params, seg.ParamName), append(parser.segs, seg), processedPart
n, seg = parser.analyseParameterPart(pattern, customConstraints...)
parser.params, parser.segs = append(parser.params, seg.ParamName), append(parser.segs, seg)
} else {
processedPart, seg := parser.analyseConstantPart(pattern, nextParamPosition)
parser.segs, part = append(parser.segs, seg), processedPart
n, seg = parser.analyseConstantPart(pattern, nextParamPosition)
parser.segs = append(parser.segs, seg)
}
// reduce the pattern by the processed parts
if len(part) == len(pattern) {
break
}
pattern = pattern[len(part):]
pattern = pattern[n:]
}
// mark last segment
if len(parser.segs) > 0 {
@ -283,7 +276,7 @@ func findNextParamPosition(pattern string) int {
}
// analyseConstantPart find the end of the constant part and create the route segment
func (*routeParser) analyseConstantPart(pattern string, nextParamPosition int) (string, *routeSegment) {
func (*routeParser) analyseConstantPart(pattern string, nextParamPosition int) (int, *routeSegment) {
// handle the constant part
processedPart := pattern
if nextParamPosition != -1 {
@ -291,14 +284,14 @@ func (*routeParser) analyseConstantPart(pattern string, nextParamPosition int) (
processedPart = pattern[:nextParamPosition]
}
constPart := RemoveEscapeChar(processedPart)
return processedPart, &routeSegment{
return len(processedPart), &routeSegment{
Const: constPart,
Length: len(constPart),
}
}
// analyseParameterPart find the parameter end and create the route segment
func (routeParser *routeParser) analyseParameterPart(pattern string, customConstraints ...CustomConstraint) (string, *routeSegment) {
func (routeParser *routeParser) analyseParameterPart(pattern string, customConstraints ...CustomConstraint) (int, *routeSegment) {
isWildCard := pattern[0] == wildcardParam
isPlusParam := pattern[0] == plusParam
@ -329,6 +322,7 @@ func (routeParser *routeParser) analyseParameterPart(pattern string, customConst
// cut params part
processedPart := pattern[0 : parameterEndPosition+1]
n := parameterEndPosition + 1
paramName := RemoveEscapeChar(GetTrimmedParam(processedPart))
// Check has constraint
@ -402,7 +396,7 @@ func (routeParser *routeParser) analyseParameterPart(pattern string, customConst
segment.Constraints = constraints
}
return processedPart, segment
return n, segment
}
// isInCharset check is the given character in the charset list
@ -618,7 +612,7 @@ func GetTrimmedParam(param string) string {
return param[start:end]
}
// RemoveEscapeChar remove escape characters
// RemoveEscapeChar removes escape characters
func RemoveEscapeChar(word string) string {
b := []byte(word)
dst := 0
@ -632,6 +626,18 @@ func RemoveEscapeChar(word string) string {
return string(b[:dst])
}
// RemoveEscapeCharBytes removes escape characters
func RemoveEscapeCharBytes(word []byte) []byte {
dst := 0
for src := 0; src < len(word); src++ {
if word[src] != '\\' {
word[dst] = word[src]
dst++
}
}
return word[:dst]
}
func getParamConstraintType(constraintPart string) TypeConstraint {
switch constraintPart {
case ConstraintInt: