Skip to content

Commit 48afdc3

Browse files
committed
refactor(path): optimization cleanPath
1 parent 09ee538 commit 48afdc3

File tree

2 files changed

+50
-31
lines changed

2 files changed

+50
-31
lines changed

path.go

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,29 @@ package gin
1919
//
2020
// If the result of this process is an empty string, "/" is returned.
2121
func cleanPath(p string) string {
22-
const stackBufSize = 128
2322
// Turn empty string into "/"
2423
if p == "" {
2524
return "/"
2625
}
2726

27+
n := len(p)
28+
29+
// If the path length is 1, handle special cases separately:
30+
// - If it is "/" or ".", return "/".
31+
// - Otherwise, prepend "/" to the path and return it.
32+
if n == 1 {
33+
if p[0] == '/' || p[0] == '.' {
34+
return "/"
35+
}
36+
return "/" + p
37+
}
38+
39+
const stackBufSize = 128
40+
2841
// Reasonably sized buffer on stack to avoid allocations in the common case.
2942
// If a larger buffer is required, it gets allocated dynamically.
3043
buf := make([]byte, 0, stackBufSize)
3144

32-
n := len(p)
33-
3445
// Invariants:
3546
// reading from path; r is index of next byte to process.
3647
// writing to buf; w is index of next byte to write.
@@ -39,7 +50,7 @@ func cleanPath(p string) string {
3950
r := 1
4051
w := 1
4152

42-
if p[0] != '/' || (n > 1 && (p[1] == '/' || p[1] == '\\')) {
53+
if p[0] != '/' {
4354
r = 0
4455

4556
if n+1 > stackBufSize {
@@ -50,46 +61,52 @@ func cleanPath(p string) string {
5061
buf[0] = '/'
5162
}
5263

53-
trailing := n > 1 && p[n-1] == '/'
64+
var trailing bool
5465

5566
// A bit more clunky without a 'lazybuf' like the path package, but the loop
5667
// gets completely inlined (bufApp calls).
5768
// loop has no expensive function calls (except 1x make) // So in contrast to the path package this loop has no expensive function
5869
// calls (except make, if needed).
5970

6071
for r < n {
61-
switch {
62-
case p[r] == '/':
72+
switch p[r] {
73+
case '/':
6374
// empty path element, trailing slash is added after the end
6475
r++
6576

66-
case p[r] == '.' && r+1 == n:
67-
trailing = true
68-
r++
69-
70-
case p[r] == '.' && p[r+1] == '/':
71-
// . element
72-
r += 2
73-
74-
case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):
75-
// .. element: remove to last /
76-
r += 3
77-
78-
if w > 1 {
79-
// can backtrack
80-
w--
81-
82-
if len(buf) == 0 {
83-
for w > 1 && p[w] != '/' {
84-
w--
85-
}
86-
} else {
87-
for w > 1 && buf[w] != '/' {
77+
case '.':
78+
if r+1 == n {
79+
trailing = true
80+
r++
81+
// Reduce one comparison between r and n
82+
goto endOfLoop
83+
}
84+
switch p[r+1] {
85+
case '/':
86+
// . element
87+
r += 2
88+
89+
case '.':
90+
if r+2 == n || p[r+2] == '/' {
91+
// .. element: remove to last /
92+
r += 3
93+
94+
if w > 1 {
95+
// can backtrack
8896
w--
97+
98+
if len(buf) == 0 {
99+
for w > 1 && p[w] != '/' {
100+
w--
101+
}
102+
} else {
103+
for w > 1 && buf[w] != '/' {
104+
w--
105+
}
106+
}
89107
}
90108
}
91109
}
92-
93110
default:
94111
// Real path element.
95112
// Add slash if needed
@@ -107,8 +124,9 @@ func cleanPath(p string) string {
107124
}
108125
}
109126

127+
endOfLoop:
110128
// Re-append trailing slash
111-
if trailing && w > 1 {
129+
if (trailing || p[n-1] == '/') && w > 1 {
112130
bufApp(&buf, p, w, '/')
113131
w++
114132
}

path_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ var cleanTests = []cleanPathTest{
2727

2828
// missing root
2929
{"", "/"},
30+
{"a", "/a"},
3031
{"a/", "/a/"},
3132
{"abc", "/abc"},
3233
{"abc/def", "/abc/def"},

0 commit comments

Comments
 (0)