Reduce allocations in Walkdir (#17036)

This commit is contained in:
Klaus Post
2023-04-15 10:25:25 -07:00
committed by GitHub
parent dd9ed85e22
commit 839b9c9271
3 changed files with 174 additions and 10 deletions

View File

@@ -209,6 +209,9 @@ func checkObjectNameForLengthAndSlash(bucket, object string) error {
// SlashSeparator - slash separator.
const SlashSeparator = "/"
// SlashSeparatorChar - slash separator.
const SlashSeparatorChar = '/'
// retainSlash - retains slash from a path.
func retainSlash(s string) string {
if s == "" {
@@ -231,13 +234,102 @@ func pathsJoinPrefix(prefix string, elem ...string) (paths []string) {
func pathJoin(elem ...string) string {
trailingSlash := ""
if len(elem) > 0 {
if HasSuffix(elem[len(elem)-1], SlashSeparator) {
if hasSuffixByte(elem[len(elem)-1], SlashSeparatorChar) {
trailingSlash = SlashSeparator
}
}
return path.Join(elem...) + trailingSlash
}
// pathJoinBuf - like path.Join() but retains trailing SlashSeparator of the last element.
// Provide a string builder to reduce allocation.
func pathJoinBuf(dst *bytes.Buffer, elem ...string) string {
trailingSlash := len(elem) > 0 && hasSuffixByte(elem[len(elem)-1], SlashSeparatorChar)
dst.Reset()
added := 0
for _, e := range elem {
if added > 0 || e != "" {
if added > 0 {
dst.WriteRune(SlashSeparatorChar)
}
dst.WriteString(e)
added += len(e)
}
}
if pathNeedsClean(dst.Bytes()) {
s := path.Clean(dst.String())
if trailingSlash {
return s + SlashSeparator
}
return s
}
if trailingSlash {
dst.WriteRune(SlashSeparatorChar)
}
return dst.String()
}
// hasSuffixByte returns true if the last byte of s is 'suffix'
func hasSuffixByte(s string, suffix byte) bool {
return len(s) > 0 && s[len(s)-1] == suffix
}
// pathNeedsClean returns whether path.Clean may change the path.
// Will detect all cases that will be cleaned,
// but may produce false positives on non-trivial paths.
func pathNeedsClean(path []byte) bool {
if len(path) == 0 {
return true
}
rooted := path[0] == '/'
n := len(path)
r, w := 0, 0
if rooted {
r, w = 1, 1
}
for r < n {
switch {
case path[r] > 127:
// Non ascii.
return true
case path[r] == '/':
// multiple / elements
return true
case path[r] == '.' && (r+1 == n || path[r+1] == '/'):
// . element - assume it has to be cleaned.
return true
case path[r] == '.' && path[r+1] == '.' && (r+2 == n || path[r+2] == '/'):
// .. element: remove to last / - assume it has to be cleaned.
return true
default:
// real path element.
// add slash if needed
if rooted && w != 1 || !rooted && w != 0 {
w++
}
// copy element
for ; r < n && path[r] != '/'; r++ {
w++
}
// allow one slash, not at end
if r < n-1 && path[r] == '/' {
r++
}
}
}
// Turn empty string into "."
if w == 0 {
return true
}
return false
}
// mustGetUUID - get a random UUID.
func mustGetUUID() string {
u, err := uuid.NewRandom()