我正在尝试有效地测试接口{}是否实现给定的函数,我的解决方案是创建一个只有这个函数的接口,然后检查接口{}是否实现了这个单一的函数接口.这里的两个选项似乎是使用反射或类型断言.两者似乎都具有相同的行为,但速度差异很大.
查看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)
Go中的类型断言依赖于一个名为runtime.assertE2I2的函数.如果您查看代码,您会注意到它getitab依赖于additab(在同一文件中)依赖的代码.
现在,如果给定类型实现了内部的接口检查实际的逻辑additab是完全一样Implements的reflect-线性搜索,这是即使在此评论中指出:
// 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实际上利用了缓存 - 类型断言的结果存储在哈希映射中,因此相同类型的后续类型断言将在恒定时间内运行,这就是为什么您会看到性能上的巨大差异.