reduce CPU usage upto 50% in readdir (#10466)

This commit is contained in:
Klaus Post 2020-09-14 17:19:54 -07:00 committed by GitHub
parent 0104af6bcc
commit b1c99e88ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 35 additions and 35 deletions

View file

@ -119,7 +119,7 @@ func readAllFileInfo(ctx context.Context, disks []StorageAPI, bucket, object, ve
metadataArray := make([]FileInfo, len(disks))
g := errgroup.WithNErrs(len(disks))
// Read `xl.meta` parallelly across disks.
// Read `xl.meta` in parallel across disks.
for index := range disks {
index := index
g.Go(func() (err error) {

View file

@ -19,6 +19,7 @@
package cmd
import (
"bytes"
"fmt"
"os"
"sync"
@ -42,14 +43,14 @@ var direntPool = sync.Pool{
// value used to represent a syscall.DT_UNKNOWN Dirent.Type.
const unexpectedFileMode os.FileMode = os.ModeNamedPipe | os.ModeSocket | os.ModeDevice
func parseDirEnt(buf []byte) (consumed int, name string, typ os.FileMode, err error) {
func parseDirEnt(buf []byte) (consumed int, name []byte, typ os.FileMode, err error) {
// golang.org/issue/15653
dirent := (*syscall.Dirent)(unsafe.Pointer(&buf[0]))
if v := unsafe.Offsetof(dirent.Reclen) + unsafe.Sizeof(dirent.Reclen); uintptr(len(buf)) < v {
return consumed, name, typ, fmt.Errorf("buf size of %d smaller than dirent header size %d", len(buf), v)
return consumed, nil, typ, fmt.Errorf("buf size of %d smaller than dirent header size %d", len(buf), v)
}
if len(buf) < int(dirent.Reclen) {
return consumed, name, typ, fmt.Errorf("buf size %d < record length %d", len(buf), dirent.Reclen)
return consumed, nil, typ, fmt.Errorf("buf size %d < record length %d", len(buf), dirent.Reclen)
}
consumed = int(dirent.Reclen)
if direntInode(dirent) == 0 { // File absent in directory.
@ -72,11 +73,10 @@ func parseDirEnt(buf []byte) (consumed int, name string, typ os.FileMode, err er
nameBuf := (*[unsafe.Sizeof(dirent.Name)]byte)(unsafe.Pointer(&dirent.Name[0]))
nameLen, err := direntNamlen(dirent)
if err != nil {
return consumed, name, typ, err
return consumed, nil, typ, err
}
name = string(nameBuf[:nameLen])
return consumed, name, typ, nil
return consumed, nameBuf[:nameLen], typ, nil
}
// Return all the entries at the directory dirPath.
@ -116,13 +116,13 @@ func readDirFilterFn(dirPath string, filter func(name string, typ os.FileMode) e
return err
}
boff += consumed
if name == "" || name == "." || name == ".." {
if len(name) == 0 || bytes.Equal(name, []byte{'.'}) || bytes.Equal(name, []byte{'.', '.'}) {
continue
}
if typ&os.ModeSymlink == os.ModeSymlink {
continue
}
if err = filter(name, typ); err == errDoneForNow {
if err = filter(string(name), typ); err == errDoneForNow {
// filtering requested to return by caller.
return nil
}
@ -143,6 +143,10 @@ func readDirN(dirPath string, count int) (entries []string, err error) {
bufp := direntPool.Get().(*[]byte)
defer direntPool.Put(bufp)
nameTmp := direntPool.Get().(*[]byte)
defer direntPool.Put(nameTmp)
tmp := *nameTmp
boff := 0 // starting read position in buf
nbuf := 0 // end valid data in buf
@ -165,14 +169,14 @@ func readDirN(dirPath string, count int) (entries []string, err error) {
return nil, err
}
boff += consumed
if name == "" || name == "." || name == ".." {
if len(name) == 0 || bytes.Equal(name, []byte{'.'}) || bytes.Equal(name, []byte{'.', '.'}) {
continue
}
// Fallback for filesystems (like old XFS) that don't
// support Dirent.Type and have DT_UNKNOWN (0) there
// instead.
if typ == unexpectedFileMode {
fi, err := os.Lstat(pathJoin(dirPath, name))
fi, err := os.Lstat(pathJoin(dirPath, string(name)))
if err != nil {
// It got deleted in the meantime, not found
// or returns too many symlinks ignore this
@ -189,9 +193,13 @@ func readDirN(dirPath string, count int) (entries []string, err error) {
continue
}
if typ.IsRegular() {
entries = append(entries, name)
entries = append(entries, string(name))
} else if typ.IsDir() {
entries = append(entries, name+SlashSeparator)
// Use temp buffer to append a slash to avoid string concat.
tmp = tmp[:len(name)+1]
copy(tmp, name)
tmp[len(tmp)-1] = '/' // SlashSeparator
entries = append(entries, string(tmp))
}
count--
}

View file

@ -29,29 +29,20 @@ type TreeWalkResult struct {
}
// Return entries that have prefix prefixEntry.
// Note: input entries are expected to be sorted.
// The supplied entries are modified and the returned string is a subslice of entries.
func filterMatchingPrefix(entries []string, prefixEntry string) []string {
start := 0
end := len(entries)
for {
if start == end {
break
}
if HasPrefix(entries[start], prefixEntry) {
break
}
start++
if len(entries) == 0 || prefixEntry == "" {
return entries
}
for {
if start == end {
break
// Write to the beginning of entries.
dst := entries[:0]
for _, s := range entries {
if !HasPrefix(s, prefixEntry) {
continue
}
if HasPrefix(entries[end-1], prefixEntry) {
break
}
end--
dst = append(dst, s)
}
return entries[start:end]
return dst
}
// xl.ListDir returns entries with trailing "/" for directories. At the object layer
@ -101,12 +92,12 @@ type IsLeafFunc func(string, string) bool
type IsLeafDirFunc func(string, string) bool
func filterListEntries(bucket, prefixDir string, entries []string, prefixEntry string, isLeaf IsLeafFunc) ([]string, bool) {
// Listing needs to be sorted.
sort.Strings(entries)
// Filter entries that have the prefix prefixEntry.
entries = filterMatchingPrefix(entries, prefixEntry)
// Listing needs to be sorted.
sort.Strings(entries)
// Can isLeaf() check be delayed till when it has to be sent down the
// TreeWalkResult channel?
delayIsLeaf := delayIsLeafCheck(entries)
@ -124,6 +115,7 @@ func filterListEntries(bucket, prefixDir string, entries []string, prefixEntry s
// Sort again after removing trailing "/" for objects as the previous sort
// does not hold good anymore.
sort.Strings(entries)
return entries, false
}