go/src/os/dir_unix.go

211 lines
5 KiB
Go
Raw Normal View History

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build aix || dragonfly || freebsd || (js && wasm) || wasip1 || linux || netbsd || openbsd || solaris
package os
import (
"internal/byteorder"
"internal/goarch"
"io"
os: use poller for file I/O This changes the os package to use the runtime poller for file I/O where possible. When a system call blocks on a pollable descriptor, the goroutine will be blocked on the poller but the thread will be released to run other goroutines. When using a non-pollable descriptor, the os package will continue to use thread-blocking system calls as before. For example, on GNU/Linux, the runtime poller uses epoll. epoll does not support ordinary disk files, so they will continue to use blocking I/O as before. The poller will be used for pipes. Since this means that the poller is used for many more programs, this modifies the runtime to only block waiting for the poller if there is some goroutine that is waiting on the poller. Otherwise, there is no point, as the poller will never make any goroutine ready. This preserves the runtime's current simple deadlock detection. This seems to crash FreeBSD systems, so it is disabled on FreeBSD. This is issue 19093. Using the poller on Windows requires opening the file with FILE_FLAG_OVERLAPPED. We should only do that if we can remove that flag if the program calls the Fd method. This is issue 19098. Update #6817. Update #7903. Update #15021. Update #18507. Update #19093. Update #19098. Change-Id: Ia5197dcefa7c6fbcca97d19a6f8621b2abcbb1fe Reviewed-on: https://go-review.googlesource.com/36800 Run-TryBot: Ian Lance Taylor <iant@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Russ Cox <rsc@golang.org>
2017-02-10 15:17:38 -08:00
"runtime"
os: reuse readdir buffers on unix with a sync.Pool (*File).readdir allocates a fixed-size 8KiB buffer on unix systems that cannot be reused. While it accounts for just a single allocation, it's more than large enough to show up in profiles and make things quite a bit slower. Instead of allocating so often, use a sync.Pool to allow these buffers to be reused. This has a large impact on readdir heavy workloads. Package os benchmarks: name old time/op new time/op delta Readdirname-12 35.6µs ± 5% 18.1µs ± 4% -49.00% (p=0.000 n=10+10) Readdir-12 142µs ± 1% 121µs ± 0% -14.87% (p=0.000 n=10+9) ReadDir-12 44.0µs ± 6% 28.4µs ± 8% -35.58% (p=0.000 n=9+10) name old alloc/op new alloc/op delta Readdirname-12 14.4kB ± 0% 6.2kB ± 0% -57.08% (p=0.000 n=10+10) Readdir-12 41.6kB ± 0% 33.4kB ± 0% -19.77% (p=0.000 n=10+9) ReadDir-12 21.9kB ± 0% 13.7kB ± 0% -37.39% (p=0.000 n=10+10) name old allocs/op new allocs/op delta Readdirname-12 131 ± 0% 130 ± 0% -0.76% (p=0.000 n=10+10) Readdir-12 367 ± 0% 366 ± 0% -0.27% (p=0.000 n=10+10) ReadDir-12 249 ± 0% 248 ± 0% -0.40% (p=0.000 n=10+10) A clunky benchmark I threw together that calls filepath.WalkDir on $GOMODCACHE: name old time/op new time/op delta WalkDir-12 91.2ms ±19% 48.7ms ± 0% -46.54% (p=0.000 n=10+10) name old alloc/op new alloc/op delta WalkDir-12 54.0MB ± 0% 7.6MB ± 0% -85.92% (p=0.000 n=8+9) name old allocs/op new allocs/op delta WalkDir-12 136k ± 0% 130k ± 0% -4.15% (p=0.000 n=8+8) Change-Id: I00e4d48726da0e46c528ab205409afd03127b844 Reviewed-on: https://go-review.googlesource.com/c/go/+/302169 Reviewed-by: Ian Lance Taylor <iant@golang.org> Reviewed-by: Emmanuel Odeke <emmanuel@orijtech.com> Run-TryBot: Emmanuel Odeke <emmanuel@orijtech.com> TryBot-Result: Go Bot <gobot@golang.org>
2021-03-16 12:35:35 +10:30
"sync"
"syscall"
"unsafe"
)
// Auxiliary information if the File describes a directory
type dirInfo struct {
mu sync.Mutex
os: reuse readdir buffers on unix with a sync.Pool (*File).readdir allocates a fixed-size 8KiB buffer on unix systems that cannot be reused. While it accounts for just a single allocation, it's more than large enough to show up in profiles and make things quite a bit slower. Instead of allocating so often, use a sync.Pool to allow these buffers to be reused. This has a large impact on readdir heavy workloads. Package os benchmarks: name old time/op new time/op delta Readdirname-12 35.6µs ± 5% 18.1µs ± 4% -49.00% (p=0.000 n=10+10) Readdir-12 142µs ± 1% 121µs ± 0% -14.87% (p=0.000 n=10+9) ReadDir-12 44.0µs ± 6% 28.4µs ± 8% -35.58% (p=0.000 n=9+10) name old alloc/op new alloc/op delta Readdirname-12 14.4kB ± 0% 6.2kB ± 0% -57.08% (p=0.000 n=10+10) Readdir-12 41.6kB ± 0% 33.4kB ± 0% -19.77% (p=0.000 n=10+9) ReadDir-12 21.9kB ± 0% 13.7kB ± 0% -37.39% (p=0.000 n=10+10) name old allocs/op new allocs/op delta Readdirname-12 131 ± 0% 130 ± 0% -0.76% (p=0.000 n=10+10) Readdir-12 367 ± 0% 366 ± 0% -0.27% (p=0.000 n=10+10) ReadDir-12 249 ± 0% 248 ± 0% -0.40% (p=0.000 n=10+10) A clunky benchmark I threw together that calls filepath.WalkDir on $GOMODCACHE: name old time/op new time/op delta WalkDir-12 91.2ms ±19% 48.7ms ± 0% -46.54% (p=0.000 n=10+10) name old alloc/op new alloc/op delta WalkDir-12 54.0MB ± 0% 7.6MB ± 0% -85.92% (p=0.000 n=8+9) name old allocs/op new allocs/op delta WalkDir-12 136k ± 0% 130k ± 0% -4.15% (p=0.000 n=8+8) Change-Id: I00e4d48726da0e46c528ab205409afd03127b844 Reviewed-on: https://go-review.googlesource.com/c/go/+/302169 Reviewed-by: Ian Lance Taylor <iant@golang.org> Reviewed-by: Emmanuel Odeke <emmanuel@orijtech.com> Run-TryBot: Emmanuel Odeke <emmanuel@orijtech.com> TryBot-Result: Go Bot <gobot@golang.org>
2021-03-16 12:35:35 +10:30
buf *[]byte // buffer for directory I/O
nbuf int // length of buf; return value from Getdirentries
bufp int // location of next record in buf.
}
const (
// More than 5760 to work around https://golang.org/issue/24015.
blockSize = 8192
)
os: reuse readdir buffers on unix with a sync.Pool (*File).readdir allocates a fixed-size 8KiB buffer on unix systems that cannot be reused. While it accounts for just a single allocation, it's more than large enough to show up in profiles and make things quite a bit slower. Instead of allocating so often, use a sync.Pool to allow these buffers to be reused. This has a large impact on readdir heavy workloads. Package os benchmarks: name old time/op new time/op delta Readdirname-12 35.6µs ± 5% 18.1µs ± 4% -49.00% (p=0.000 n=10+10) Readdir-12 142µs ± 1% 121µs ± 0% -14.87% (p=0.000 n=10+9) ReadDir-12 44.0µs ± 6% 28.4µs ± 8% -35.58% (p=0.000 n=9+10) name old alloc/op new alloc/op delta Readdirname-12 14.4kB ± 0% 6.2kB ± 0% -57.08% (p=0.000 n=10+10) Readdir-12 41.6kB ± 0% 33.4kB ± 0% -19.77% (p=0.000 n=10+9) ReadDir-12 21.9kB ± 0% 13.7kB ± 0% -37.39% (p=0.000 n=10+10) name old allocs/op new allocs/op delta Readdirname-12 131 ± 0% 130 ± 0% -0.76% (p=0.000 n=10+10) Readdir-12 367 ± 0% 366 ± 0% -0.27% (p=0.000 n=10+10) ReadDir-12 249 ± 0% 248 ± 0% -0.40% (p=0.000 n=10+10) A clunky benchmark I threw together that calls filepath.WalkDir on $GOMODCACHE: name old time/op new time/op delta WalkDir-12 91.2ms ±19% 48.7ms ± 0% -46.54% (p=0.000 n=10+10) name old alloc/op new alloc/op delta WalkDir-12 54.0MB ± 0% 7.6MB ± 0% -85.92% (p=0.000 n=8+9) name old allocs/op new allocs/op delta WalkDir-12 136k ± 0% 130k ± 0% -4.15% (p=0.000 n=8+8) Change-Id: I00e4d48726da0e46c528ab205409afd03127b844 Reviewed-on: https://go-review.googlesource.com/c/go/+/302169 Reviewed-by: Ian Lance Taylor <iant@golang.org> Reviewed-by: Emmanuel Odeke <emmanuel@orijtech.com> Run-TryBot: Emmanuel Odeke <emmanuel@orijtech.com> TryBot-Result: Go Bot <gobot@golang.org>
2021-03-16 12:35:35 +10:30
var dirBufPool = sync.Pool{
New: func() any {
os: reuse readdir buffers on unix with a sync.Pool (*File).readdir allocates a fixed-size 8KiB buffer on unix systems that cannot be reused. While it accounts for just a single allocation, it's more than large enough to show up in profiles and make things quite a bit slower. Instead of allocating so often, use a sync.Pool to allow these buffers to be reused. This has a large impact on readdir heavy workloads. Package os benchmarks: name old time/op new time/op delta Readdirname-12 35.6µs ± 5% 18.1µs ± 4% -49.00% (p=0.000 n=10+10) Readdir-12 142µs ± 1% 121µs ± 0% -14.87% (p=0.000 n=10+9) ReadDir-12 44.0µs ± 6% 28.4µs ± 8% -35.58% (p=0.000 n=9+10) name old alloc/op new alloc/op delta Readdirname-12 14.4kB ± 0% 6.2kB ± 0% -57.08% (p=0.000 n=10+10) Readdir-12 41.6kB ± 0% 33.4kB ± 0% -19.77% (p=0.000 n=10+9) ReadDir-12 21.9kB ± 0% 13.7kB ± 0% -37.39% (p=0.000 n=10+10) name old allocs/op new allocs/op delta Readdirname-12 131 ± 0% 130 ± 0% -0.76% (p=0.000 n=10+10) Readdir-12 367 ± 0% 366 ± 0% -0.27% (p=0.000 n=10+10) ReadDir-12 249 ± 0% 248 ± 0% -0.40% (p=0.000 n=10+10) A clunky benchmark I threw together that calls filepath.WalkDir on $GOMODCACHE: name old time/op new time/op delta WalkDir-12 91.2ms ±19% 48.7ms ± 0% -46.54% (p=0.000 n=10+10) name old alloc/op new alloc/op delta WalkDir-12 54.0MB ± 0% 7.6MB ± 0% -85.92% (p=0.000 n=8+9) name old allocs/op new allocs/op delta WalkDir-12 136k ± 0% 130k ± 0% -4.15% (p=0.000 n=8+8) Change-Id: I00e4d48726da0e46c528ab205409afd03127b844 Reviewed-on: https://go-review.googlesource.com/c/go/+/302169 Reviewed-by: Ian Lance Taylor <iant@golang.org> Reviewed-by: Emmanuel Odeke <emmanuel@orijtech.com> Run-TryBot: Emmanuel Odeke <emmanuel@orijtech.com> TryBot-Result: Go Bot <gobot@golang.org>
2021-03-16 12:35:35 +10:30
// The buffer must be at least a block long.
buf := make([]byte, blockSize)
return &buf
},
}
func (d *dirInfo) close() {
if d.buf != nil {
dirBufPool.Put(d.buf)
d.buf = nil
}
}
func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
// If this file has no dirInfo, create one.
var d *dirInfo
for {
d = f.dirinfo.Load()
if d != nil {
break
}
newD := new(dirInfo)
if f.dirinfo.CompareAndSwap(nil, newD) {
d = newD
break
}
}
d.mu.Lock()
defer d.mu.Unlock()
if d.buf == nil {
d.buf = dirBufPool.Get().(*[]byte)
}
// Change the meaning of n for the implementation below.
//
// The n above was for the public interface of "if n <= 0,
// Readdir returns all the FileInfo from the directory in a
// single slice".
//
// But below, we use only negative to mean looping until the
// end and positive to mean bounded, with positive
// terminating at 0.
if n == 0 {
n = -1
}
for n != 0 {
// Refill the buffer if necessary
if d.bufp >= d.nbuf {
d.bufp = 0
var errno error
os: reuse readdir buffers on unix with a sync.Pool (*File).readdir allocates a fixed-size 8KiB buffer on unix systems that cannot be reused. While it accounts for just a single allocation, it's more than large enough to show up in profiles and make things quite a bit slower. Instead of allocating so often, use a sync.Pool to allow these buffers to be reused. This has a large impact on readdir heavy workloads. Package os benchmarks: name old time/op new time/op delta Readdirname-12 35.6µs ± 5% 18.1µs ± 4% -49.00% (p=0.000 n=10+10) Readdir-12 142µs ± 1% 121µs ± 0% -14.87% (p=0.000 n=10+9) ReadDir-12 44.0µs ± 6% 28.4µs ± 8% -35.58% (p=0.000 n=9+10) name old alloc/op new alloc/op delta Readdirname-12 14.4kB ± 0% 6.2kB ± 0% -57.08% (p=0.000 n=10+10) Readdir-12 41.6kB ± 0% 33.4kB ± 0% -19.77% (p=0.000 n=10+9) ReadDir-12 21.9kB ± 0% 13.7kB ± 0% -37.39% (p=0.000 n=10+10) name old allocs/op new allocs/op delta Readdirname-12 131 ± 0% 130 ± 0% -0.76% (p=0.000 n=10+10) Readdir-12 367 ± 0% 366 ± 0% -0.27% (p=0.000 n=10+10) ReadDir-12 249 ± 0% 248 ± 0% -0.40% (p=0.000 n=10+10) A clunky benchmark I threw together that calls filepath.WalkDir on $GOMODCACHE: name old time/op new time/op delta WalkDir-12 91.2ms ±19% 48.7ms ± 0% -46.54% (p=0.000 n=10+10) name old alloc/op new alloc/op delta WalkDir-12 54.0MB ± 0% 7.6MB ± 0% -85.92% (p=0.000 n=8+9) name old allocs/op new allocs/op delta WalkDir-12 136k ± 0% 130k ± 0% -4.15% (p=0.000 n=8+8) Change-Id: I00e4d48726da0e46c528ab205409afd03127b844 Reviewed-on: https://go-review.googlesource.com/c/go/+/302169 Reviewed-by: Ian Lance Taylor <iant@golang.org> Reviewed-by: Emmanuel Odeke <emmanuel@orijtech.com> Run-TryBot: Emmanuel Odeke <emmanuel@orijtech.com> TryBot-Result: Go Bot <gobot@golang.org>
2021-03-16 12:35:35 +10:30
d.nbuf, errno = f.pfd.ReadDirent(*d.buf)
os: use poller for file I/O This changes the os package to use the runtime poller for file I/O where possible. When a system call blocks on a pollable descriptor, the goroutine will be blocked on the poller but the thread will be released to run other goroutines. When using a non-pollable descriptor, the os package will continue to use thread-blocking system calls as before. For example, on GNU/Linux, the runtime poller uses epoll. epoll does not support ordinary disk files, so they will continue to use blocking I/O as before. The poller will be used for pipes. Since this means that the poller is used for many more programs, this modifies the runtime to only block waiting for the poller if there is some goroutine that is waiting on the poller. Otherwise, there is no point, as the poller will never make any goroutine ready. This preserves the runtime's current simple deadlock detection. This seems to crash FreeBSD systems, so it is disabled on FreeBSD. This is issue 19093. Using the poller on Windows requires opening the file with FILE_FLAG_OVERLAPPED. We should only do that if we can remove that flag if the program calls the Fd method. This is issue 19098. Update #6817. Update #7903. Update #15021. Update #18507. Update #19093. Update #19098. Change-Id: Ia5197dcefa7c6fbcca97d19a6f8621b2abcbb1fe Reviewed-on: https://go-review.googlesource.com/36800 Run-TryBot: Ian Lance Taylor <iant@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Russ Cox <rsc@golang.org>
2017-02-10 15:17:38 -08:00
runtime.KeepAlive(f)
if errno != nil {
return names, dirents, infos, &PathError{Op: "readdirent", Path: f.name, Err: errno}
}
if d.nbuf <= 0 {
// Optimization: we can return the buffer to the pool, there is nothing else to read.
dirBufPool.Put(d.buf)
d.buf = nil
break // EOF
}
}
// Drain the buffer
os: reuse readdir buffers on unix with a sync.Pool (*File).readdir allocates a fixed-size 8KiB buffer on unix systems that cannot be reused. While it accounts for just a single allocation, it's more than large enough to show up in profiles and make things quite a bit slower. Instead of allocating so often, use a sync.Pool to allow these buffers to be reused. This has a large impact on readdir heavy workloads. Package os benchmarks: name old time/op new time/op delta Readdirname-12 35.6µs ± 5% 18.1µs ± 4% -49.00% (p=0.000 n=10+10) Readdir-12 142µs ± 1% 121µs ± 0% -14.87% (p=0.000 n=10+9) ReadDir-12 44.0µs ± 6% 28.4µs ± 8% -35.58% (p=0.000 n=9+10) name old alloc/op new alloc/op delta Readdirname-12 14.4kB ± 0% 6.2kB ± 0% -57.08% (p=0.000 n=10+10) Readdir-12 41.6kB ± 0% 33.4kB ± 0% -19.77% (p=0.000 n=10+9) ReadDir-12 21.9kB ± 0% 13.7kB ± 0% -37.39% (p=0.000 n=10+10) name old allocs/op new allocs/op delta Readdirname-12 131 ± 0% 130 ± 0% -0.76% (p=0.000 n=10+10) Readdir-12 367 ± 0% 366 ± 0% -0.27% (p=0.000 n=10+10) ReadDir-12 249 ± 0% 248 ± 0% -0.40% (p=0.000 n=10+10) A clunky benchmark I threw together that calls filepath.WalkDir on $GOMODCACHE: name old time/op new time/op delta WalkDir-12 91.2ms ±19% 48.7ms ± 0% -46.54% (p=0.000 n=10+10) name old alloc/op new alloc/op delta WalkDir-12 54.0MB ± 0% 7.6MB ± 0% -85.92% (p=0.000 n=8+9) name old allocs/op new allocs/op delta WalkDir-12 136k ± 0% 130k ± 0% -4.15% (p=0.000 n=8+8) Change-Id: I00e4d48726da0e46c528ab205409afd03127b844 Reviewed-on: https://go-review.googlesource.com/c/go/+/302169 Reviewed-by: Ian Lance Taylor <iant@golang.org> Reviewed-by: Emmanuel Odeke <emmanuel@orijtech.com> Run-TryBot: Emmanuel Odeke <emmanuel@orijtech.com> TryBot-Result: Go Bot <gobot@golang.org>
2021-03-16 12:35:35 +10:30
buf := (*d.buf)[d.bufp:d.nbuf]
reclen, ok := direntReclen(buf)
if !ok || reclen > uint64(len(buf)) {
break
}
rec := buf[:reclen]
d.bufp += int(reclen)
ino, ok := direntIno(rec)
if !ok {
break
}
// When building to wasip1, the host runtime might be running on Windows
// or might expose a remote file system which does not have the concept
// of inodes. Therefore, we cannot make the assumption that it is safe
// to skip entries with zero inodes.
// Some Linux filesystems (old XFS, FUSE) can return valid files with zero inodes.
if ino == 0 && runtime.GOOS != "linux" && runtime.GOOS != "wasip1" {
continue
}
const namoff = uint64(unsafe.Offsetof(syscall.Dirent{}.Name))
namlen, ok := direntNamlen(rec)
if !ok || namoff+namlen > uint64(len(rec)) {
break
}
name := rec[namoff : namoff+namlen]
for i, c := range name {
if c == 0 {
name = name[:i]
break
}
}
// Check for useless names before allocating a string.
if string(name) == "." || string(name) == ".." {
continue
}
if n > 0 { // see 'n == 0' comment above
n--
}
if mode == readdirName {
names = append(names, string(name))
} else if mode == readdirDirEntry {
de, err := newUnixDirent(f.name, string(name), direntType(rec))
if IsNotExist(err) {
// File disappeared between readdir and stat.
// Treat as if it didn't exist.
continue
}
if err != nil {
return nil, dirents, nil, err
}
dirents = append(dirents, de)
} else {
info, err := lstat(f.name + "/" + string(name))
if IsNotExist(err) {
// File disappeared between readdir + stat.
// Treat as if it didn't exist.
continue
}
if err != nil {
return nil, nil, infos, err
}
infos = append(infos, info)
}
}
if n > 0 && len(names)+len(dirents)+len(infos) == 0 {
return nil, nil, nil, io.EOF
}
return names, dirents, infos, nil
}
// readInt returns the size-bytes unsigned integer in native byte order at offset off.
func readInt(b []byte, off, size uintptr) (u uint64, ok bool) {
if len(b) < int(off+size) {
return 0, false
}
if goarch.BigEndian {
return readIntBE(b[off:], size), true
}
return readIntLE(b[off:], size), true
}
func readIntBE(b []byte, size uintptr) uint64 {
switch size {
case 1:
return uint64(b[0])
case 2:
return uint64(byteorder.BEUint16(b))
case 4:
return uint64(byteorder.BEUint32(b))
case 8:
return uint64(byteorder.BEUint64(b))
default:
panic("syscall: readInt with unsupported size")
}
}
func readIntLE(b []byte, size uintptr) uint64 {
switch size {
case 1:
return uint64(b[0])
case 2:
return uint64(byteorder.LEUint16(b))
case 4:
return uint64(byteorder.LEUint32(b))
case 8:
return uint64(byteorder.LEUint64(b))
default:
panic("syscall: readInt with unsupported size")
}
}