From c2e39b75703219f6d1fcec11ab4b6f8aaa87c2f0 Mon Sep 17 00:00:00 2001 From: Kashiwa <13825170+ksw2000@users.noreply.github.com> Date: Mon, 31 Mar 2025 15:49:40 +0800 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20Add=20findNext?= =?UTF-8?q?NonEscapedCharPosition=20for=20single-byte=20charset=20cases=20?= =?UTF-8?q?(#3378)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ♻️ Refactor: add findNextNonEscapedCharsetPosition to process a single-byte parameter ``` goos: linux goarch: amd64 pkg: github.com/gofiber/fiber/v3 cpu: AMD EPYC 9J14 96-Core Processor │ old.txt │ new.txt │ │ sec/op │ sec/op vs base │ _RoutePatternMatch//api/v1/const_|_match_|_/api/v1/const-16 160.4n ± 1% 159.0n ± 0% -0.84% (p=0.000 n=20) _RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1-16 151.6n ± 0% 150.8n ± 0% -0.53% (p=0.005 n=20) _RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1/-16 151.7n ± 0% 150.6n ± 0% -0.73% (p=0.000 n=20) _RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1/something-16 162.3n ± 0% 160.8n ± 0% -0.96% (p=0.000 n=20) _RoutePatternMatch//api/:param/fixedEnd_|_match_|_/api/abc/fixedEnd-16 452.9n ± 1% 435.8n ± 0% -3.79% (p=0.000 n=20) _RoutePatternMatch//api/:param/fixedEnd_|_not_match_|_/api/abc/def/fixedEnd-16 455.6n ± 1% 435.7n ± 0% -4.38% (p=0.000 n=20) _RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity-16 524.4n ± 1% 507.6n ± 1% -3.19% (p=0.000 n=20) _RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity/-16 528.2n ± 0% 508.7n ± 0% -3.69% (p=0.000 n=20) _RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity/1-16 528.1n ± 0% 510.6n ± 0% -3.31% (p=0.000 n=20) _RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v-16 500.3n ± 0% 489.0n ± 0% -2.27% (p=0.000 n=20) _RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v2-16 502.1n ± 0% 489.9n ± 0% -2.44% (p=0.000 n=20) _RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v1/-16 515.5n ± 0% 498.8n ± 0% -3.24% (p=0.000 n=20) geomean 339.4n 331.1n -2.46% │ old.txt │ new.txt │ │ B/op │ B/op vs base │ _RoutePatternMatch//api/v1/const_|_match_|_/api/v1/const-16 144.0 ± 0% 144.0 ± 0% ~ (p=1.000 n=20) ¹ _RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1-16 136.0 ± 0% 136.0 ± 0% ~ (p=1.000 n=20) ¹ _RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1/-16 136.0 ± 0% 136.0 ± 0% ~ (p=1.000 n=20) ¹ _RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1/something-16 152.0 ± 0% 152.0 ± 0% ~ (p=1.000 n=20) ¹ _RoutePatternMatch//api/:param/fixedEnd_|_match_|_/api/abc/fixedEnd-16 368.0 ± 0% 368.0 ± 0% ~ (p=1.000 n=20) ¹ _RoutePatternMatch//api/:param/fixedEnd_|_not_match_|_/api/abc/def/fixedEnd-16 368.0 ± 0% 368.0 ± 0% ~ (p=1.000 n=20) ¹ _RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity-16 432.0 ± 0% 432.0 ± 0% ~ (p=1.000 n=20) ¹ _RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity/-16 432.0 ± 0% 432.0 ± 0% ~ (p=1.000 n=20) ¹ _RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity/1-16 432.0 ± 0% 432.0 ± 0% ~ (p=1.000 n=20) ¹ _RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v-16 424.0 ± 0% 424.0 ± 0% ~ (p=1.000 n=20) ¹ _RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v2-16 424.0 ± 0% 424.0 ± 0% ~ (p=1.000 n=20) ¹ _RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v1/-16 424.0 ± 0% 424.0 ± 0% ~ (p=1.000 n=20) ¹ geomean 288.8 288.8 +0.00% ¹ all samples are equal │ old.txt │ new.txt │ │ allocs/op │ allocs/op vs base │ _RoutePatternMatch//api/v1/const_|_match_|_/api/v1/const-16 4.000 ± 0% 4.000 ± 0% ~ (p=1.000 n=20) ¹ _RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1-16 4.000 ± 0% 4.000 ± 0% ~ (p=1.000 n=20) ¹ _RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1/-16 4.000 ± 0% 4.000 ± 0% ~ (p=1.000 n=20) ¹ _RoutePatternMatch//api/v1/const_|_not_match_|_/api/v1/something-16 4.000 ± 0% 4.000 ± 0% ~ (p=1.000 n=20) ¹ _RoutePatternMatch//api/:param/fixedEnd_|_match_|_/api/abc/fixedEnd-16 9.000 ± 0% 9.000 ± 0% ~ (p=1.000 n=20) ¹ _RoutePatternMatch//api/:param/fixedEnd_|_not_match_|_/api/abc/def/fixedEnd-16 9.000 ± 0% 9.000 ± 0% ~ (p=1.000 n=20) ¹ _RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity-16 9.000 ± 0% 9.000 ± 0% ~ (p=1.000 n=20) ¹ _RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity/-16 9.000 ± 0% 9.000 ± 0% ~ (p=1.000 n=20) ¹ _RoutePatternMatch//api/v1/:param/*_|_match_|_/api/v1/entity/1-16 9.000 ± 0% 9.000 ± 0% ~ (p=1.000 n=20) ¹ _RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v-16 9.000 ± 0% 9.000 ± 0% ~ (p=1.000 n=20) ¹ _RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v2-16 9.000 ± 0% 9.000 ± 0% ~ (p=1.000 n=20) ¹ _RoutePatternMatch//api/v1/:param/*_|_not_match_|_/api/v1/-16 9.000 ± 0% 9.000 ± 0% ~ (p=1.000 n=20) ¹ geomean 6.868 6.868 +0.00% ¹ all samples are equal ``` --- path.go | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/path.go b/path.go index b188a41c..8cfde73f 100644 --- a/path.go +++ b/path.go @@ -123,16 +123,6 @@ var ( parameterDelimiterChars = append([]byte{paramStarterChar, escapeChar}, routeDelimiter...) // list of chars to find the end of a parameter parameterEndChars = append([]byte{optionalParam}, parameterDelimiterChars...) - // list of parameter constraint start - parameterConstraintStartChars = []byte{paramConstraintStart} - // list of parameter constraint end - parameterConstraintEndChars = []byte{paramConstraintEnd} - // list of parameter separator - parameterConstraintSeparatorChars = []byte{paramConstraintSeparator} - // list of parameter constraint data start - parameterConstraintDataStartChars = []byte{paramConstraintDataStart} - // list of parameter constraint data separator - parameterConstraintDataSeparatorChars = []byte{paramConstraintDataSeparator} ) // RoutePatternMatch checks if a given path matches a Fiber route pattern. @@ -337,8 +327,8 @@ func (parser *routeParser) analyseParameterPart(pattern string, customConstraint // find constraint part if exists in the parameter part and remove it if parameterEndPosition > 0 { - parameterConstraintStart = findNextNonEscapedCharsetPosition(pattern[0:parameterEndPosition], parameterConstraintStartChars) - parameterConstraintEnd = strings.LastIndexByte(pattern[0:parameterEndPosition+1], paramConstraintEnd) + parameterConstraintStart = findNextNonEscapedCharPosition(pattern[:parameterEndPosition], paramConstraintStart) + parameterConstraintEnd = strings.LastIndexByte(pattern[:parameterEndPosition+1], paramConstraintEnd) } // cut params part @@ -351,11 +341,11 @@ func (parser *routeParser) analyseParameterPart(pattern string, customConstraint if hasConstraint := parameterConstraintStart != -1 && parameterConstraintEnd != -1; hasConstraint { constraintString := pattern[parameterConstraintStart+1 : parameterConstraintEnd] - userConstraints := splitNonEscaped(constraintString, string(parameterConstraintSeparatorChars)) + userConstraints := splitNonEscaped(constraintString, paramConstraintSeparator) constraints = make([]*Constraint, 0, len(userConstraints)) for _, c := range userConstraints { - start := findNextNonEscapedCharsetPosition(c, parameterConstraintDataStartChars) + start := findNextNonEscapedCharPosition(c, paramConstraintDataStart) end := strings.LastIndexByte(c, paramConstraintDataEnd) // Assign constraint @@ -368,7 +358,7 @@ func (parser *routeParser) analyseParameterPart(pattern string, customConstraint // remove escapes from data if constraint.ID != regexConstraint { - constraint.Data = splitNonEscaped(c[start+1:end], string(parameterConstraintDataSeparatorChars)) + constraint.Data = splitNonEscaped(c[start+1:end], paramConstraintDataSeparator) if len(constraint.Data) == 1 { constraint.Data[0] = RemoveEscapeChar(constraint.Data[0]) } else if len(constraint.Data) == 2 { // This is fine, we simply expect two parts @@ -432,11 +422,11 @@ func findNextCharsetPosition(search string, charset []byte) int { return nextPosition } -// findNextCharsetPositionConstraint search the next char position from the charset +// findNextCharsetPositionConstraint searches the next char position from the charset // unlike findNextCharsetPosition, it takes care of constraint start-end chars to parse route pattern func findNextCharsetPositionConstraint(search string, charset []byte) int { - constraintStart := findNextNonEscapedCharsetPosition(search, parameterConstraintStartChars) - constraintEnd := findNextNonEscapedCharsetPosition(search, parameterConstraintEndChars) + constraintStart := findNextNonEscapedCharPosition(search, paramConstraintStart) + constraintEnd := findNextNonEscapedCharPosition(search, paramConstraintEnd) nextPosition := -1 for _, char := range charset { @@ -452,7 +442,7 @@ func findNextCharsetPositionConstraint(search string, charset []byte) int { return nextPosition } -// findNextNonEscapedCharsetPosition search the next char position from the charset and skip the escaped characters +// findNextNonEscapedCharsetPosition searches the next char position from the charset and skips the escaped characters func findNextNonEscapedCharsetPosition(search string, charset []byte) int { pos := findNextCharsetPosition(search, charset) for pos > 0 && search[pos-1] == escapeChar { @@ -471,16 +461,26 @@ func findNextNonEscapedCharsetPosition(search string, charset []byte) int { return pos } +// findNextNonEscapedCharPosition searches the next char position and skips the escaped characters +func findNextNonEscapedCharPosition(search string, char byte) int { + for i := 0; i < len(search); i++ { + if search[i] == char && (i == 0 || search[i-1] != escapeChar) { + return i + } + } + return -1 +} + // splitNonEscaped slices s into all substrings separated by sep and returns a slice of the substrings between those separators // This function also takes a care of escape char when splitting. -func splitNonEscaped(s, sep string) []string { +func splitNonEscaped(s string, sep byte) []string { var result []string - i := findNextNonEscapedCharsetPosition(s, []byte(sep)) + i := findNextNonEscapedCharPosition(s, sep) for i > -1 { result = append(result, s[:i]) - s = s[i+len(sep):] - i = findNextNonEscapedCharsetPosition(s, []byte(sep)) + s = s[i+1:] + i = findNextNonEscapedCharPosition(s, sep) } return append(result, s)