cmd/guru: re-use buffer for reading files

I felt guilty about leaving 2% on the table in CL 108878,
so I thought I'd get it a different way.

Teach readFile to accept a re-usable bytes.Buffer to read into,
to reduce the amount of garbage created.

To limit the possible memory impact of giant files,
only re-use the buffer for the duration of a single package.
Even that is enough to help.

name       old time/op       new time/op       delta
Referrers        4.67s ± 3%        4.58s ± 2%  -1.96%  (p=0.029 n=10+10)

name       old user-time/op  new user-time/op  delta
Referrers        16.5s ± 3%        15.8s ± 1%  -4.39%  (p=0.000 n=10+8)

name       old sys-time/op   new sys-time/op   delta
Referrers        16.1s ± 3%        15.9s ± 3%    ~     (p=0.218 n=10+10)

This work supported by Sourcegraph.

Change-Id: I594ef25c0fd5ccb766ff5b98dbbd1a75a7a4f957
Reviewed-on: https://go-review.googlesource.com/108935
Reviewed-by: Alan Donovan <adonovan@google.com>
This commit is contained in:
Josh Bleecher Snyder 2018-04-23 12:43:41 -07:00
parent c1f4e2c6dc
commit 5c8013c561
1 changed files with 11 additions and 5 deletions

View File

@ -436,12 +436,15 @@ func globalReferrersPkgLevel(q *Query, obj types.Object, fset *token.FileSet) er
deffiles = make(map[string]*ast.File)
}
buf := new(bytes.Buffer) // reusable buffer for reading files
for _, file := range files {
if !buildutil.IsAbsPath(q.Build, file) {
file = buildutil.JoinPath(q.Build, pkg.Dir, file)
}
buf.Reset()
sema <- struct{}{} // acquire token
src, err := readFile(q.Build, file)
src, err := readFile(q.Build, file, buf)
<-sema // release token
if err != nil {
continue
@ -731,7 +734,7 @@ func (r *referrersPackageResult) foreachRef(f func(id *ast.Ident, text string))
// start asynchronous read.
go func() {
sema <- struct{}{} // acquire token
content, err := readFile(r.build, posn.Filename)
content, err := readFile(r.build, posn.Filename, nil)
<-sema // release token
if err != nil {
fi.data <- err
@ -769,14 +772,17 @@ func (r *referrersPackageResult) foreachRef(f func(id *ast.Ident, text string))
// readFile is like ioutil.ReadFile, but
// it goes through the virtualized build.Context.
func readFile(ctxt *build.Context, filename string) ([]byte, error) {
// If non-nil, buf must have been reset.
func readFile(ctxt *build.Context, filename string, buf *bytes.Buffer) ([]byte, error) {
rc, err := buildutil.OpenFile(ctxt, filename)
if err != nil {
return nil, err
}
defer rc.Close()
var buf bytes.Buffer
if _, err := io.Copy(&buf, rc); err != nil {
if buf == nil {
buf = new(bytes.Buffer)
}
if _, err := io.Copy(buf, rc); err != nil {
return nil, err
}
return buf.Bytes(), nil