mirror of
https://github.com/golang/go
synced 2024-11-19 01:24:39 -07:00
edf8e6fef8
This brings goimports from 160ms to 100ms on my laptop, and under 50ms on my Linux machine. Using cmd/trace, I noticed that filepath.Walk is inherently slow. See https://golang.org/issue/16399 for details. Instead, this CL introduces a new (private) filepath.Walk implementation, optimized for speed and avoiding unnecessary work. In addition to avoid an Lstat per file, it also reads directories concurrently. The old goimports code did that too, but now that logic is removed from goimports and the code is simplified. This also adds some profiling command line flags to goimports that I found useful. Updates golang/go#16367 (goimports is slow) Updates golang/go#16399 (filepath.Walk is slow) Change-Id: I708d570cbaad3fa9ad75a12054f5a932ee159b84 Reviewed-on: https://go-review.googlesource.com/25001 Reviewed-by: Andrew Gerrand <adg@golang.org> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
123 lines
3.2 KiB
Go
123 lines
3.2 KiB
Go
// Copyright 2016 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.
|
|
|
|
// +build linux darwin freebsd openbsd netbsd
|
|
|
|
package imports
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"syscall"
|
|
"unsafe"
|
|
)
|
|
|
|
const blockSize = 8 << 10
|
|
|
|
// unknownFileMode is a sentinel (and bogus) os.FileMode
|
|
// value used to represent a syscall.DT_UNKNOWN Dirent.Type.
|
|
const unknownFileMode os.FileMode = os.ModeNamedPipe | os.ModeSocket | os.ModeDevice
|
|
|
|
func readDir(dirName string, fn func(dirName, entName string, typ os.FileMode) error) error {
|
|
fd, err := syscall.Open(dirName, 0, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer syscall.Close(fd)
|
|
|
|
// The buffer must be at least a block long.
|
|
buf := make([]byte, blockSize) // stack-allocated; doesn't escape
|
|
bufp := 0 // starting read position in buf
|
|
nbuf := 0 // end valid data in buf
|
|
for {
|
|
if bufp >= nbuf {
|
|
bufp = 0
|
|
nbuf, err = syscall.ReadDirent(fd, buf)
|
|
if err != nil {
|
|
return os.NewSyscallError("readdirent", err)
|
|
}
|
|
if nbuf <= 0 {
|
|
return nil
|
|
}
|
|
}
|
|
consumed, name, typ := parseDirEnt(buf[bufp:nbuf])
|
|
bufp += consumed
|
|
if name == "" || name == "." || name == ".." {
|
|
continue
|
|
}
|
|
// Fallback for filesystems (like old XFS) that don't
|
|
// support Dirent.Type and have DT_UNKNOWN (0) there
|
|
// instead.
|
|
if typ == unknownFileMode {
|
|
fi, err := os.Lstat(dirName + "/" + name)
|
|
if err != nil {
|
|
// It got deleted in the meantime.
|
|
if os.IsNotExist(err) {
|
|
continue
|
|
}
|
|
return err
|
|
}
|
|
typ = fi.Mode() & os.ModeType
|
|
}
|
|
if err := fn(dirName, name, typ); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
func parseDirEnt(buf []byte) (consumed int, name string, typ os.FileMode) {
|
|
// 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 {
|
|
panic(fmt.Sprintf("buf size of %d smaller than dirent header size %d", len(buf), v))
|
|
}
|
|
if len(buf) < int(dirent.Reclen) {
|
|
panic(fmt.Sprintf("buf size %d < record length %d", len(buf), dirent.Reclen))
|
|
}
|
|
consumed = int(dirent.Reclen)
|
|
if direntInode(dirent) == 0 { // File absent in directory.
|
|
return
|
|
}
|
|
switch dirent.Type {
|
|
case syscall.DT_REG:
|
|
typ = 0
|
|
case syscall.DT_DIR:
|
|
typ = os.ModeDir
|
|
case syscall.DT_LNK:
|
|
typ = os.ModeSymlink
|
|
case syscall.DT_BLK:
|
|
typ = os.ModeDevice
|
|
case syscall.DT_FIFO:
|
|
typ = os.ModeNamedPipe
|
|
case syscall.DT_SOCK:
|
|
typ = os.ModeSocket
|
|
case syscall.DT_UNKNOWN:
|
|
typ = unknownFileMode
|
|
default:
|
|
// Skip weird things.
|
|
// It's probably a DT_WHT (http://lwn.net/Articles/325369/)
|
|
// or something. Revisit if/when this package is moved outside
|
|
// of goimports. goimports only cares about regular files,
|
|
// symlinks, and directories.
|
|
return
|
|
}
|
|
|
|
nameBuf := (*[unsafe.Sizeof(dirent.Name)]byte)(unsafe.Pointer(&dirent.Name[0]))
|
|
nameLen := bytes.IndexByte(nameBuf[:], 0)
|
|
if nameLen < 0 {
|
|
panic("failed to find terminating 0 byte in dirent")
|
|
}
|
|
|
|
// Special cases for common things:
|
|
if nameLen == 1 && nameBuf[0] == '.' {
|
|
name = "."
|
|
} else if nameLen == 2 && nameBuf[0] == '.' && nameBuf[1] == '.' {
|
|
name = ".."
|
|
} else {
|
|
name = string(nameBuf[:nameLen])
|
|
}
|
|
return
|
|
}
|