Contents
  1. 1. 定义四种操作场景
  2. 2. Benchmark 测试函数
  3. 3. 运行测试
  4. 4. 其它

由于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%之间, 还是不可忽略的。

Contents
  1. 1. 定义四种操作场景
  2. 2. Benchmark 测试函数
  3. 3. 运行测试
  4. 4. 其它