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

Go 为什么reflect Type.Implements()比类型断言慢得多?,go,reflection,Go,Reflection,我试图有效地测试一个接口{}是否实现了给定的函数,我的解决方案是创建一个只有这个函数的接口,然后检查接口{}是否实现了这个单一函数接口。这里的两个选项似乎要么使用反射,要么使用类型断言。两者似乎有相同的行为,但有一个很大的速度差异 查看Value.Implements()的代码,它对值上定义的函数进行线性扫描,并将它们与接口进行比较。 然而,类型断言似乎只是做一个常量时间比较(与接口中的函数数量无关) Implements()不只是执行类型断言有什么原因吗 基准: package benchma

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

查看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()
        }
    }
}

Go中的类型断言依赖于调用的函数。如果查看代码,您会注意到它依赖于
getitab
,而getitab又依赖于
additab
(在同一个文件中)

现在,检查给定类型是否在
additab
中实现接口的实际逻辑与
reflect
中的
implements
完全相同,这是一种线性搜索,甚至在本注释中指出:

// 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).

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

Implements()不进行缓存有什么原因吗?这表明它期望某个实现接口的东西是否可以更改(但是如果这是真的,那么类型断言缓存肯定不正确?),因为反射速度很慢。@bradleyjkemp,我建议参考“计算Itable”部分。我想回答您的评论是:即使类型断言/切换并将具体类型的值分配给接口类型的变量可能需要在运行时为该类型计算新的itable,编译器假定一个合理的程序中可能只有有限数量的此类计算,这仅仅是因为Go程序在运行时无法改变自身…@bradleyjkemp…这就是运行时除了使用
reflect
包之外设置itab动态计算的原因:后者可以在任意情况下使用用不可预测的方式来断言任何事情。因此,在前一种情况下,计算ITAB并缓存它们可以被视为修改常规AoT编译的即时编译,-使用
reflect
包是一种更高级别的、本质上更随意的操作。我觉得“为什么”这个问题不是我们真正能回答的问题,除了投机,也许是一个更好的地方来筹集资金?也就是说,我在所有方面都同意@kostix的观点——它更有可能有很多类型断言,而不是很多反射调用。当您需要大量调用时,哈希映射会有所帮助,但也会增加恒定的开销,这在调用很少时更为明显,而且您不需要对反射调用超过几个。
// 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).