Golang仿函數實現方法及效率測試

在C++ STL中,仿函數(functors)被大量用作改變算法的內在行爲。
由於Golang不支持泛型,所以沒法像C++那樣靈活的使用仿函數。但是Golang有interface,函數是”一等公民”(可賦值給指定類型變量),因此,在Golang中實際上也可以像仿函數那樣,通過具有相同參數和返回值的方法聲明的不同對象,實現行爲的差異化。
下面,詳細講述,Golang中的實現方法:
以下通過不同方法實現 Lesser(int,int)bool 和 Greater(int,int)bool的不同行爲舉例。
1. 通過interface實現

type Comparer interface {
    F(left, right int) bool
}

//create cmp object by name
func CreateComparer(cmpName string) (r Comparer) {
    switch cmpName {
    case "": //default Lesser
        fallthrough
    case "Lesser":
        r = Lesser{}
    case "Greater":
        r = Greater{}
    default: //unsupport name
        panic(cmpName)
    }
    return
}

//Lesser
type Lesser struct{}

func (this Lesser) F(left, right int) (ok bool) {
    ok = left < right
    return
}

//Greater
type Greater struct{}

func (this Greater) F(left, right int) (ok bool) {
    ok = right < left
    return
}

2.通過函數對象實現

type CmpFunc func(left, right int) bool

//create cmp object by name
func GetCmpFunc(cmpName string) (r CmpFunc) {
    switch cmpName {
    case "": //default Lesser
        fallthrough
    case "Lesser":
        r = Less
    case "Greater":
        r = Great
    default: //unsupport name
        panic(cmpName)
    }
    return
}

//Lesser
func Less(left, right int) (ok bool) {
    ok = left < right
    return
}

//Greater
func Great(left, right int) (ok bool) {
    ok = right < left
    return
}

3.通過轉調對象實現

type CmpObj byte

const (
    CMP_LESS CmpObj = iota
    CMP_GREAT
)

func (me CmpObj) F(left, right int) (ok bool) {
    switch me {
    case CMP_LESS:
        ok = Less(left, right)
    case CMP_GREAT:
        ok = Great(left, right)
    default:
        panic(me)
    }
    return
}

以下是3種方法的效率測試:

var (
    cmp1 = CreateComparer("Lesser")
    cmp2 = GetCmpFunc("Lesser")
    cmp3 = CMP_LESS
    N    = 100000000
)

func TestSize(t *testing.T) {
    fmt.Println("Interface", unsafe.Sizeof(cmp1))
    fmt.Println("Func", unsafe.Sizeof(cmp2))
    fmt.Println("Obj", unsafe.Sizeof(cmp3))
}

func Benchmark_Interface(b *testing.B) {
    for i := 0; i < N; i++ {
        cmp1.F(1, 2)
    }
}
func Benchmark_Func(b *testing.B) {
    for i := 0; i < N; i++ {
        cmp2(1, 2)
    }
}
func Benchmark_Obj(b *testing.B) {
    for i := 0; i < N; i++ {
        cmp3.F(1, 2)
    }
}

//Interface 16
//Func 8
//Obj 1
//Benchmark_Interface-4     1000000000           0.55 ns/op
//Benchmark_Func-4          2000000000           0.19 ns/op
//Benchmark_Obj-4           2000000000           0.24 ns/op

結論如下:
用interface實現多態,會佔用兩個指針(16字節空間) 執行效率上 大概慢一倍
使用函數指針 佔用1個指針(8字節) 執行效率最高 但使用起來不夠靈活
使用轉調對象 只需要1個字節 執行效率跟函數指針差不多

推薦使用第三種方法(轉調對象)的方法,使用數值枚舉標識多路分發邏輯,效率上幾乎沒有損失,對外部引用對象的空間需求也比較小。

我將以上測試代碼放在這裏,歡迎查閱:
https://github.com/vipally/glab/blob/master/lab5/lab5_test.go

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章