为什么反映Type.Implements()比类型断言慢得多?

bra*_*emp 3 reflection go

我正在尝试有效地测试接口{}是否实现给定的函数,我的解决方案是创建一个只有这个函数的接口,然后检查接口{}是否实现了这个单一的函数接口.这里的两个选项似乎是使用反射或类型断言.两者似乎都具有相同的行为,但速度差异很大.

查看Value.Implements()的代码,它对值上定义的函数进行线性扫描,并将它们与接口进行比较.然而,类型断言似乎只进行了一个恒定的时间比较(与接口中的函数数量无关).

Implements()不只是做一个类型断言是有原因的吗?

基准测试:

package benchmarks

import (
    "reflect"
    "testing"
)

type ITest interface {
    Foo()
}

type Base struct{}

func (Base) A() {}
func (Base) B() {}
func (Base) C() {}
func (Base) D() {}
func (Base) E() {}
func (Base) F() {}
func (Base) G() {}
func (Base) H() {}
func (Base) I() {}
func (Base) J() {}

var Interface = reflect.TypeOf((*ITest)(nil)).Elem()

func BenchmarkReflection(b *testing.B) {
    var iface interface{}
    iface = Base{}
    for i := 0; i < b.N; i++ {
        if reflect.TypeOf(iface).Implements(Interface) {
            b.FailNow()
        }
    }
}

func BenchmarkAssertion(b *testing.B) {
    var iface interface{}
    iface = Base{}
    for i := 0; i < b.N; i++ {
        if _, ok := iface.(ITest); ok {
            b.FailNow()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

结果:

go test -run=XXX -bench=. so_test.go
goos: linux
goarch: amd64
BenchmarkReflection-8           10000000                  208 ns/op
BenchmarkAssertion-8            200000000                9.24 ns/op
PASS
ok      command-line-arguments  5.115s
Run Code Online (Sandbox Code Playgroud)

fst*_*nis 8

Go中的类型断言依赖于一个名为runtime.assertE2I2的函数.如果您查看代码,您会注意到它getitab依赖于additab(在同一文件中)依赖的代码.

现在,如果给定类型实现了内部的接口检查实际的逻辑additab是完全一样Implementsreflect-线性搜索,这是即使在此评论中指出:

// both inter and typ have method sorted by name,
// and interface names are unique,
// so can iterate over both in lock step;
// the loop is O(ni+nt) not O(ni*nt).
Run Code Online (Sandbox Code Playgroud)

但是,不同之处在于additab实际上利用了缓存 - 类型断言的结果存储在哈希映射中,因此相同类型的后续类型断言将在恒定时间内运行,这就是为什么您会看到性能上的巨大差异.