2017-10-19 55 views
3

我試圖高效地測試一個接口{}是否實現了給定的函數,我的解決方案是僅使用此函數創建接口,然後檢查接口是否實現了此單一函數接口。這兩個選項似乎是使用反射或類型斷言。兩者似乎都有相同的行爲,但速度差異很大。爲什麼反映Type.Implements()比類型斷言慢得多?

查看Value.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 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 

回答

7

類型斷言依賴於一個名爲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). 

然而,區別在於additab實際上使用了緩存 - 類型斷言的結果存儲在散列映射中,因此,對於相同類型的後續類型斷言將在恆定時間內運行,這就是爲什麼您看到性能存在巨大差異。

+0

是否有理由爲什麼實現()不做緩存?它表明它期望某個接口的實現是否可以改變(然而,如果這是真的,那麼類型斷言緩存肯定是不正確的?) – bradleyjkemp

+0

因爲反射速度很慢。 – Volker

+1

@bradleyjkemp,我建議參考「計算Itable」部分[這裏](https://research.swtch.com/interfaces)。我的回答是:即使類型斷言/開關並將具體類型的值賦給接口類型的變量,可能需要在運行時爲該類型計算新的可用屬性,編譯器會假定可能只有一個有限在一個明智的程序中這樣計算的數量 - 僅僅是因爲Go程序在運行時不能改變它自己... – kostix

相關問題