Skip to content

Commit ddd8bc1

Browse files
committed
go/types, types2: optimize instance lookup in LookupFieldOrMethod
LookupFieldOrMethod appears as a hotspot when benchmarking gopls' auto-completion. In particular, instanceLookup.add was allocating in the common case of structs with no embedding. This is easily fixed, by using a small array in front of the map inside of instanceLookup. Do this, and additionally add a microbenchmark. The benchmark improvement is significant: name old time/op new time/op delta LookupFieldOrMethod-12 388µs ± 6% 154µs ± 3% -60.36% (p=0.000 n=10+10) name old alloc/op new alloc/op delta LookupFieldOrMethod-12 152kB ± 0% 2kB ± 0% -98.77% (p=0.000 n=9+10) name old allocs/op new allocs/op delta LookupFieldOrMethod-12 1.41k ± 0% 0.07k ± 0% -95.38% (p=0.000 n=10+10) It should also be noted that instanceLookup is used elsewhere, in particular by validType. In those contexts, the scope is not just the current type but the entire package, and so the newly added buffer is likely to simply cause extra Identical checks. Nevertheless, those checks are cheap, and on balance the improved LookupFieldOrMethod performance leads overall to improved type-checking performance. Standard library benchmark results varied by package, but type checking speed for many packages improved by ~5%, with allocations improved by ~10%. If this weren't the case we could let the caller control the buffer size, but that optimization doesn't seem necessary at this time. For example: Check/http/funcbodies/noinfo-12 71.5ms ± 4% 67.3ms ± 2% -5.90% (p=0.000 n=20+20) Check/http/funcbodies/noinfo-12 244k ± 0% 219k ± 0% -10.36% (p=0.000 n=19+19) Updates #53992 Change-Id: I10b6deb3819ab562dbbe1913f12b977cf956dd50 Reviewed-on: https://go-review.googlesource.com/c/go/+/423935 Run-TryBot: Robert Findley <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Robert Griesemer <[email protected]>
1 parent 6f445a9 commit ddd8bc1

File tree

4 files changed

+143
-2
lines changed

4 files changed

+143
-2
lines changed

src/cmd/compile/internal/types2/lookup.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,10 +261,18 @@ func lookupType(m map[Type]int, typ Type) (int, bool) {
261261
}
262262

263263
type instanceLookup struct {
264-
m map[*Named][]*Named
264+
// buf is used to avoid allocating the map m in the common case of a small
265+
// number of instances.
266+
buf [3]*Named
267+
m map[*Named][]*Named
265268
}
266269

267270
func (l *instanceLookup) lookup(inst *Named) *Named {
271+
for _, t := range l.buf {
272+
if t != nil && Identical(inst, t) {
273+
return t
274+
}
275+
}
268276
for _, t := range l.m[inst.Origin()] {
269277
if Identical(inst, t) {
270278
return t
@@ -274,6 +282,12 @@ func (l *instanceLookup) lookup(inst *Named) *Named {
274282
}
275283

276284
func (l *instanceLookup) add(inst *Named) {
285+
for i, t := range l.buf {
286+
if t == nil {
287+
l.buf[i] = inst
288+
return
289+
}
290+
}
277291
if l.m == nil {
278292
l.m = make(map[*Named][]*Named)
279293
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package types2_test
6+
7+
import (
8+
"path/filepath"
9+
"runtime"
10+
"testing"
11+
12+
. "cmd/compile/internal/types2"
13+
)
14+
15+
// BenchmarkLookupFieldOrMethod measures types.LookupFieldOrMethod performance.
16+
// LookupFieldOrMethod is a performance hotspot for both type-checking and
17+
// external API calls.
18+
func BenchmarkLookupFieldOrMethod(b *testing.B) {
19+
// Choose an arbitrary, large package.
20+
path := filepath.Join(runtime.GOROOT(), "src", "net", "http")
21+
22+
files, err := pkgFiles(path)
23+
if err != nil {
24+
b.Fatal(err)
25+
}
26+
27+
conf := Config{
28+
Importer: defaultImporter(),
29+
}
30+
31+
pkg, err := conf.Check("http", files, nil)
32+
if err != nil {
33+
b.Fatal(err)
34+
}
35+
36+
scope := pkg.Scope()
37+
names := scope.Names()
38+
39+
// Look up an arbitrary name for each type referenced in the package scope.
40+
lookup := func() {
41+
for _, name := range names {
42+
typ := scope.Lookup(name).Type()
43+
LookupFieldOrMethod(typ, true, pkg, "m")
44+
}
45+
}
46+
47+
// Perform a lookup once, to ensure that any lazily-evaluated state is
48+
// complete.
49+
lookup()
50+
51+
b.ResetTimer()
52+
for i := 0; i < b.N; i++ {
53+
lookup()
54+
}
55+
}

src/go/types/lookup.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,10 +261,18 @@ func lookupType(m map[Type]int, typ Type) (int, bool) {
261261
}
262262

263263
type instanceLookup struct {
264-
m map[*Named][]*Named
264+
// buf is used to avoid allocating the map m in the common case of a small
265+
// number of instances.
266+
buf [3]*Named
267+
m map[*Named][]*Named
265268
}
266269

267270
func (l *instanceLookup) lookup(inst *Named) *Named {
271+
for _, t := range l.buf {
272+
if t != nil && Identical(inst, t) {
273+
return t
274+
}
275+
}
268276
for _, t := range l.m[inst.Origin()] {
269277
if Identical(inst, t) {
270278
return t
@@ -274,6 +282,12 @@ func (l *instanceLookup) lookup(inst *Named) *Named {
274282
}
275283

276284
func (l *instanceLookup) add(inst *Named) {
285+
for i, t := range l.buf {
286+
if t == nil {
287+
l.buf[i] = inst
288+
return
289+
}
290+
}
277291
if l.m == nil {
278292
l.m = make(map[*Named][]*Named)
279293
}

src/go/types/lookup_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package types_test
6+
7+
import (
8+
"go/importer"
9+
"go/token"
10+
"path/filepath"
11+
"runtime"
12+
"testing"
13+
14+
. "go/types"
15+
)
16+
17+
// BenchmarkLookupFieldOrMethod measures types.LookupFieldOrMethod performance.
18+
// LookupFieldOrMethod is a performance hotspot for both type-checking and
19+
// external API calls.
20+
func BenchmarkLookupFieldOrMethod(b *testing.B) {
21+
// Choose an arbitrary, large package.
22+
path := filepath.Join(runtime.GOROOT(), "src", "net", "http")
23+
24+
fset := token.NewFileSet()
25+
files, err := pkgFiles(fset, path, 0)
26+
if err != nil {
27+
b.Fatal(err)
28+
}
29+
30+
conf := Config{
31+
Importer: importer.Default(),
32+
}
33+
34+
pkg, err := conf.Check("http", fset, files, nil)
35+
if err != nil {
36+
b.Fatal(err)
37+
}
38+
39+
scope := pkg.Scope()
40+
names := scope.Names()
41+
42+
// Look up an arbitrary name for each type referenced in the package scope.
43+
lookup := func() {
44+
for _, name := range names {
45+
typ := scope.Lookup(name).Type()
46+
LookupFieldOrMethod(typ, true, pkg, "m")
47+
}
48+
}
49+
50+
// Perform a lookup once, to ensure that any lazily-evaluated state is
51+
// complete.
52+
lookup()
53+
54+
b.ResetTimer()
55+
for i := 0; i < b.N; i++ {
56+
lookup()
57+
}
58+
}

0 commit comments

Comments
 (0)