imports: limit local disk concurrency, avoid reads in non-Go directories
If $GOPATH was large, or $GOPATH was $HOME and $HOME/src had many files, the unbounded concurrency in loadPkgIndex/loadPkg could make the operating system unhappy with so many threads. (sigh once again for no async file IO and needing threads for file operations) In addition, don't call go/build.Context.Import on directories that we've already determined to have no go files in them. It's just a waste of time. Makes it about 3x faster on my machine with hot caches and a big $HOME/src. Fixes golang/go#7731 LGTM=iant, adg R=golang-codereviews, iant, adg CC=david.crawshaw, golang-codereviews https://golang.org/cl/85670044
This commit is contained in:
parent
fa0f6bd591
commit
87f95283ac
|
@ -174,6 +174,16 @@ var pkgIndex struct {
|
||||||
m map[string][]pkg // shortname => []pkg, e.g "http" => "net/http"
|
m map[string][]pkg // shortname => []pkg, e.g "http" => "net/http"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gate is a semaphore for limiting concurrency.
|
||||||
|
type gate chan bool
|
||||||
|
|
||||||
|
func (g gate) enter() { g <- true }
|
||||||
|
func (g gate) leave() { <-g }
|
||||||
|
|
||||||
|
// fsgate protects the OS & filesystem from too much concurrency.
|
||||||
|
// Too much disk I/O -> too many threads -> swapping and bad scheduling.
|
||||||
|
var fsgate = make(gate, 8)
|
||||||
|
|
||||||
func loadPkgIndex() {
|
func loadPkgIndex() {
|
||||||
pkgIndex.Lock()
|
pkgIndex.Lock()
|
||||||
pkgIndex.m = make(map[string][]pkg)
|
pkgIndex.m = make(map[string][]pkg)
|
||||||
|
@ -181,13 +191,16 @@ func loadPkgIndex() {
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
for _, path := range build.Default.SrcDirs() {
|
for _, path := range build.Default.SrcDirs() {
|
||||||
|
fsgate.enter()
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
fsgate.leave()
|
||||||
fmt.Fprint(os.Stderr, err)
|
fmt.Fprint(os.Stderr, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
children, err := f.Readdir(-1)
|
children, err := f.Readdir(-1)
|
||||||
f.Close()
|
f.Close()
|
||||||
|
fsgate.leave()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprint(os.Stderr, err)
|
fmt.Fprint(os.Stderr, err)
|
||||||
continue
|
continue
|
||||||
|
@ -207,16 +220,10 @@ func loadPkgIndex() {
|
||||||
|
|
||||||
func loadPkg(wg *sync.WaitGroup, root, pkgrelpath string) {
|
func loadPkg(wg *sync.WaitGroup, root, pkgrelpath string) {
|
||||||
importpath := filepath.ToSlash(pkgrelpath)
|
importpath := filepath.ToSlash(pkgrelpath)
|
||||||
shortName := importPathToName(importpath)
|
|
||||||
|
|
||||||
dir := filepath.Join(root, importpath)
|
dir := filepath.Join(root, importpath)
|
||||||
pkgIndex.Lock()
|
|
||||||
pkgIndex.m[shortName] = append(pkgIndex.m[shortName], pkg{
|
|
||||||
importpath: importpath,
|
|
||||||
dir: dir,
|
|
||||||
})
|
|
||||||
pkgIndex.Unlock()
|
|
||||||
|
|
||||||
|
fsgate.enter()
|
||||||
|
defer fsgate.leave()
|
||||||
pkgDir, err := os.Open(dir)
|
pkgDir, err := os.Open(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -226,6 +233,11 @@ func loadPkg(wg *sync.WaitGroup, root, pkgrelpath string) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// hasGo tracks whether a directory actually appears to be a
|
||||||
|
// Go source code directory. If $GOPATH == $HOME, and
|
||||||
|
// $HOME/src has lots of other large non-Go projects in it,
|
||||||
|
// then the calls to importPathToName below can be expensive.
|
||||||
|
hasGo := false
|
||||||
for _, child := range children {
|
for _, child := range children {
|
||||||
name := child.Name()
|
name := child.Name()
|
||||||
if name == "" {
|
if name == "" {
|
||||||
|
@ -234,6 +246,9 @@ func loadPkg(wg *sync.WaitGroup, root, pkgrelpath string) {
|
||||||
if c := name[0]; c == '.' || ('0' <= c && c <= '9') {
|
if c := name[0]; c == '.' || ('0' <= c && c <= '9') {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if strings.HasSuffix(name, ".go") {
|
||||||
|
hasGo = true
|
||||||
|
}
|
||||||
if child.IsDir() {
|
if child.IsDir() {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(root, name string) {
|
go func(root, name string) {
|
||||||
|
@ -242,6 +257,16 @@ func loadPkg(wg *sync.WaitGroup, root, pkgrelpath string) {
|
||||||
}(root, filepath.Join(importpath, name))
|
}(root, filepath.Join(importpath, name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if hasGo {
|
||||||
|
shortName := importPathToName(importpath)
|
||||||
|
pkgIndex.Lock()
|
||||||
|
pkgIndex.m[shortName] = append(pkgIndex.m[shortName], pkg{
|
||||||
|
importpath: importpath,
|
||||||
|
dir: dir,
|
||||||
|
})
|
||||||
|
pkgIndex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadExports returns a list exports for a package.
|
// loadExports returns a list exports for a package.
|
||||||
|
|
Loading…
Reference in New Issue