mirror of
https://github.com/minio/minio.git
synced 2025-04-11 06:57:49 -04:00
reduce CPU usage upto 50% in readdir (#10466)
This commit is contained in:
parent
0104af6bcc
commit
b1c99e88ac
@ -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) {
|
// Write to the beginning of entries.
|
||||||
break
|
dst := entries[:0]
|
||||||
|
for _, s := range entries {
|
||||||
|
if !HasPrefix(s, prefixEntry) {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
start++
|
dst = append(dst, s)
|
||||||
}
|
}
|
||||||
for {
|
return dst
|
||||||
if start == end {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if HasPrefix(entries[end-1], prefixEntry) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
end--
|
|
||||||
}
|
|
||||||
return entries[start:end]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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…
x
Reference in New Issue
Block a user