Skip to content

Commit

Permalink
unused: use more efficient types.Implements implementation
Browse files Browse the repository at this point in the history
The standard types.Implements implementation produces a lot of garbage
in lookupFieldOrMethod. By using MethodSetCache and method sets, we can
avoid a lot of this garbage. This directly translates to lower runtimes
and lower peak memory usage.

Compared with 442643a, for std and cockroachdb, we get the following
numbers:

Total allocations
std:  6268 MB ->  4702 MB
cdb: 28398 MB -> 19048 MB

Peak rss
std:  4,556,404 ->  3,253,004
cdb: 17,403,616 -> 12,546,948

Time
std: 0:25.18 -> 0:21.84
cdb: 1:45.70 -> 1:30.74

Updates gh-394
  • Loading branch information
dominikh committed Jan 28, 2019
1 parent 3bb9299 commit 84ddbfd
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 2 deletions.
79 changes: 79 additions & 0 deletions unused/implements.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package unused

import "go/types"

// lookupMethod returns the index of and method with matching package and name, or (-1, nil).
func lookupMethod(T *types.Interface, pkg *types.Package, name string) (int, *types.Func) {
if name != "_" {
for i := 0; i < T.NumMethods(); i++ {
m := T.Method(i)
if sameId(m, pkg, name) {
return i, m
}
}
}
return -1, nil
}

func sameId(obj types.Object, pkg *types.Package, name string) bool {
// spec:
// "Two identifiers are different if they are spelled differently,
// or if they appear in different packages and are not exported.
// Otherwise, they are the same."
if name != obj.Name() {
return false
}
// obj.Name == name
if obj.Exported() {
return true
}
// not exported, so packages must be the same (pkg == nil for
// fields in Universe scope; this can only happen for types
// introduced via Eval)
if pkg == nil || obj.Pkg() == nil {
return pkg == obj.Pkg()
}
// pkg != nil && obj.pkg != nil
return pkg.Path() == obj.Pkg().Path()
}

func (c *Checker) implements(V types.Type, T *types.Interface) bool {
// fast path for common case
if T.Empty() {
return true
}

if ityp, _ := V.Underlying().(*types.Interface); ityp != nil {
for i := 0; i < T.NumMethods(); i++ {
m := T.Method(i)
_, obj := lookupMethod(ityp, m.Pkg(), m.Name())
switch {
case obj == nil:
return false
case !types.Identical(obj.Type(), m.Type()):
return false
}
}
return true
}

// A concrete type implements T if it implements all methods of T.
ms := c.msCache.MethodSet(V)
for i := 0; i < T.NumMethods(); i++ {
m := T.Method(i)
sel := ms.Lookup(m.Pkg(), m.Name())
if sel == nil {
return false
}

f, _ := sel.Obj().(*types.Func)
if f == nil {
return false
}

if !types.Identical(f.Type(), m.Type()) {
return false
}
}
return true
}
5 changes: 3 additions & 2 deletions unused/unused.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ func (c *Checker) Check(prog *lint.Program) []Unused {

unused = append(unused, Unused{Obj: obj, Position: pos})
}

return unused
}

Expand Down Expand Up @@ -557,13 +558,13 @@ func (c *Checker) processTypes(pkg *lint.Pkg) {
switch obj.Underlying().(type) {
case *types.Interface:
// pointers to interfaces have no methods, only checking non-pointer
if !types.Implements(obj, iface) {
if !c.implements(obj, iface) {
continue namedLoop
}
default:
// pointer receivers include the method set of non-pointer receivers,
// only checking pointer
if !types.Implements(objPtr, iface) {
if !c.implements(objPtr, iface) {
continue namedLoop
}
}
Expand Down

0 comments on commit 84ddbfd

Please sign in to comment.