Commit 16381b14 authored by Robert Griesemer's avatar Robert Griesemer

go/token: faster FileSet.Position implementation

- added a cache for last file looked up: avoids binary
  search if the file matches

- don't look up extra line info if not present
  (it is almost never present)

- inline one critical binary search call (inlining
  provides almost 30% improvement in this case)

Together, these changes make the go/printer benchmark
more than twice as fast (53% improvement). gofmt also
sped up by about the same amount.

Also: removed an unused internal field from FileSet.

Measurements (always best of 5 runs):

* original:
  printer.BenchmarkPrint     5    238354200 ns/op (100%)

* using last file cache:
  printer.BenchmarkPrint    10    201796600 ns/op (85%)

* avoiding lookup of extra line info:
  printer.BenchmarkPrint    10    157072700 ns/op (66%)

* inlining a critical binary search call:
  printer.BenchmarkPrint    10    111523500 ns/op (47%)

gofmt (always best of 3 runs):

* before:
  time gofmt -l src misc
  real	0m33.316s
  user	0m31.298s
  sys	0m0.319s

* after:
  time gofmt -l src misc
  real	0m15.889s
  user	0m14.596s
  sys	0m0.224s

R=r, dfc, bradfitz, rsc1
CC=golang-dev
https://golang.org/cl/4433086
parent 6af887ec
...@@ -94,10 +94,14 @@ func searchFiles(a []*File, x int) int { ...@@ -94,10 +94,14 @@ func searchFiles(a []*File, x int) int {
func (s *FileSet) file(p Pos) *File { func (s *FileSet) file(p Pos) *File {
if f := s.last; f != nil && f.base <= int(p) && int(p) <= f.base+f.size {
return f
}
if i := searchFiles(s.files, int(p)); i >= 0 { if i := searchFiles(s.files, int(p)); i >= 0 {
f := s.files[i] f := s.files[i]
// f.base <= int(p) by definition of searchFiles // f.base <= int(p) by definition of searchFiles
if int(p) <= f.base+f.size { if int(p) <= f.base+f.size {
s.last = f
return f return f
} }
} }
...@@ -316,8 +320,26 @@ func (f *File) Position(p Pos) (pos Position) { ...@@ -316,8 +320,26 @@ func (f *File) Position(p Pos) (pos Position) {
} }
func searchUints(a []int, x int) int { func searchInts(a []int, x int) int {
return sort.Search(len(a), func(i int) bool { return a[i] > x }) - 1 // This function body is a manually inlined version of:
//
// return sort.Search(len(a), func(i int) bool { return a[i] > x }) - 1
//
// With better compiler optimizations, this may not be needed in the
// future, but at the moment this change improves the go/printer
// benchmark performance by ~30%. This has a direct impact on the
// speed of gofmt and thus seems worthwhile (2011-04-29).
i, j := 0, len(a)
for i < j {
h := i + (j-i)/2 // avoid overflow when computing h
// i ≤ h < j
if a[h] <= x {
i = h + 1
} else {
j = h
}
}
return i - 1
} }
...@@ -329,14 +351,17 @@ func searchLineInfos(a []lineInfo, x int) int { ...@@ -329,14 +351,17 @@ func searchLineInfos(a []lineInfo, x int) int {
// info returns the file name, line, and column number for a file offset. // info returns the file name, line, and column number for a file offset.
func (f *File) info(offset int) (filename string, line, column int) { func (f *File) info(offset int) (filename string, line, column int) {
filename = f.name filename = f.name
if i := searchUints(f.lines, offset); i >= 0 { if i := searchInts(f.lines, offset); i >= 0 {
line, column = i+1, offset-f.lines[i]+1 line, column = i+1, offset-f.lines[i]+1
} }
if i := searchLineInfos(f.infos, offset); i >= 0 { if len(f.infos) > 0 {
alt := &f.infos[i] // almost no files have extra line infos
filename = alt.filename if i := searchLineInfos(f.infos, offset); i >= 0 {
if i := searchUints(f.lines, alt.offset); i >= 0 { alt := &f.infos[i]
line += alt.line - i - 1 filename = alt.filename
if i := searchInts(f.lines, alt.offset); i >= 0 {
line += alt.line - i - 1
}
} }
} }
return return
...@@ -348,10 +373,10 @@ func (f *File) info(offset int) (filename string, line, column int) { ...@@ -348,10 +373,10 @@ func (f *File) info(offset int) (filename string, line, column int) {
// may invoke them concurrently. // may invoke them concurrently.
// //
type FileSet struct { type FileSet struct {
mutex sync.RWMutex // protects the file set mutex sync.RWMutex // protects the file set
base int // base offset for the next file base int // base offset for the next file
files []*File // list of files in the order added to the set files []*File // list of files in the order added to the set
index map[*File]int // file -> files index for quick lookup last *File // cache of last file looked up
} }
...@@ -359,7 +384,6 @@ type FileSet struct { ...@@ -359,7 +384,6 @@ type FileSet struct {
func NewFileSet() *FileSet { func NewFileSet() *FileSet {
s := new(FileSet) s := new(FileSet)
s.base = 1 // 0 == NoPos s.base = 1 // 0 == NoPos
s.index = make(map[*File]int)
return s return s
} }
...@@ -405,8 +429,8 @@ func (s *FileSet) AddFile(filename string, base, size int) *File { ...@@ -405,8 +429,8 @@ func (s *FileSet) AddFile(filename string, base, size int) *File {
} }
// add the file to the file set // add the file to the file set
s.base = base s.base = base
s.index[f] = len(s.files)
s.files = append(s.files, f) s.files = append(s.files, f)
s.last = f
return f return f
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment