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

master
Klaus Post 4 years ago committed by GitHub
parent 0104af6bcc
commit b1c99e88ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      cmd/erasure-metadata-utils.go
  2. 32
      cmd/os-readdir_unix.go
  3. 36
      cmd/tree-walk.go

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

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

@ -29,29 +29,20 @@ type TreeWalkResult struct {
} }
// Return entries that have prefix prefixEntry. // 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 { func filterMatchingPrefix(entries []string, prefixEntry string) []string {
start := 0 if len(entries) == 0 || prefixEntry == "" {
end := len(entries) return entries
for {
if start == end {
break
}
if HasPrefix(entries[start], prefixEntry) {
break
}
start++
} }
for { // Write to the beginning of entries.
if start == end { dst := entries[:0]
break for _, s := range entries {
} if !HasPrefix(s, prefixEntry) {
if HasPrefix(entries[end-1], prefixEntry) { continue
break
} }
end-- dst = append(dst, s)
} }
return entries[start:end] return dst
} }
// xl.ListDir returns entries with trailing "/" for directories. At the object layer // 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 type IsLeafDirFunc func(string, string) bool
func filterListEntries(bucket, prefixDir string, entries []string, prefixEntry string, isLeaf IsLeafFunc) ([]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. // Filter entries that have the prefix prefixEntry.
entries = filterMatchingPrefix(entries, 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 // Can isLeaf() check be delayed till when it has to be sent down the
// TreeWalkResult channel? // TreeWalkResult channel?
delayIsLeaf := delayIsLeafCheck(entries) 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 // Sort again after removing trailing "/" for objects as the previous sort
// does not hold good anymore. // does not hold good anymore.
sort.Strings(entries) sort.Strings(entries)
return entries, false return entries, false
} }

Loading…
Cancel
Save