由于golang缺乏泛型,在许多情况下,为了代码的灵活性,难免要用到接口甚至空接口,而在对空接口中的数据进行操作之前,通常有一个类型断言的过程。我们经常听到一些“Golang”的反射性能很低的言论,那么与类型操作有关的类型断言这个操作性能到底如何呢?不妨来作一个Benchmark
定义四种操作场景
以下定义了一个类型和一个接口,分别以四种方法 具体类型
,接口
, 空接口->具体类型
, 空接口->接口
对一组数据进行求和(遍历)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| package main type Numer interface { getNum() int64 } type Num struct { num int64 } func (n *Num) getNum() int64 { return n.num } func main() { } func sumConcrete(s []*Num) int64 { var sum int64 for _, v := range s { sum += v.getNum() } return sum } func sumInterface(s []Numer) int64 { var sum int64 for _, v := range s { sum += v.getNum() } return sum } func sum2concrete(s []interface{}) int64 { var sum int64 for _, v := range s { sum += v.(*Num).getNum() } return sum } func sum2interface(s []interface{}) int64 { var sum int64 for _, v := range s { sum += v.(Numer).getNum() } return sum }
|
Benchmark 测试函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| package main import ( "math/rand" "testing" ) func BenchmarkSumT(b *testing.B) { //prepare data length := 10000 data := make([]*Num, length) its := make([]Numer, length) eits := make([]interface{}, length) for i := 0; i < length; i++ { data[i] = &Num{int64(rand.Int31n(100))} its[i] = data[i] eits[i] = data[i] } b.Run("Concrete", func(b *testing.B) { for i := 0; i < b.N; i++ { sumConcrete(data) } }) b.Run("Interface", func(b *testing.B) { for i := 0; i < b.N; i++ { sumInterface(its) } }) b.Run("i2concrete", func(b *testing.B) { for i := 0; i < b.N; i++ { sum2concrete(eits) } }) b.Run("i2interface", func(b *testing.B) { for i := 0; i < b.N; i++ { sum2interface(eits) } }) }
|
运行测试
1 2 3 4 5 6 7 8 9 10
| ➜ vscode go test -bench Sum goos: linux goarch: amd64 pkg: pc.chen/test/vscode BenchmarkSumT/Concrete-4 300000 4881 ns/op BenchmarkSumT/Interface-4 50000 24830 ns/op BenchmarkSumT/i2concrete-4 200000 7630 ns/op BenchmarkSumT/i2interface-4 10000 101115 ns/op PASS ok pc.chen/test/vscode 5.645s
|
结果有些意外,Golang推崇的接口操作相比直接操作具体类型多出4倍多时间, 更意外的是空接口
+断言
意外比正常接口要快很多!!! 只是比具体类型多出不到一倍的时间。 最后,空接口
转接口
后再调用方法是最慢的,相较接口又多出4倍多(较直接操作具体类型多出20倍多时间),这种场景现实中其实是有使用场景的,如此看来需要尽量在性能敏感的地方避免。
其它
最后,我还测试了直接用Num
实现Numer
,而不是用指针*Num
实现Numer
,结果也有微小的差异,其它情况不变的情况下,Benchmark下来成绩大概是:
1 2 3 4 5 6 7 8 9 10
| ➜ vscode go test -bench Sum goos: linux goarch: amd64 pkg: pc.chen/test/vscode BenchmarkSumT/Num-4 300000 4513 ns/op BenchmarkSumT/Numer-4 50000 30177 ns/op BenchmarkSumT/toNum-4 200000 8354 ns/op BenchmarkSumT/toNumer-4 10000 116636 ns/op PASS ok pc.chen/test/vscode 6.147s
|
如此看来使用指针实现接口性能可以高 15%-20%之间, 还是不可忽略的。