在上一篇《reflect实践与剖析》的文章中,本来打算写的更深入一点,结果发现reflect里面的东西太多了,在一篇文章中很难写全,就草草收尾了。今天谈一谈关于reflect的另一个问题:reflect为什么慢?
先问是不是,再问为什么。我们先来看一下reflect是不是真的慢。
Golang testing golang提供了一个testing
包,使得单元测试、性能测试尤为简单。只要新建一个以_test
结尾的文件,然后使用命令go test
就可以自动执行文件中的相应测试函数了(单元测试函数以Test开头,性能测试函数以Benchmark开头)。除了单元测试和性能测试,testing
还有其他的功能,暂且按住不表。下面主要介绍一下单元测试和性能测试怎么写。
单元测试 比如我们写了一个简单的浮点数除法函数并编写单元测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func Division (a, b float64 ) (float64 , error) { return a/b, nil } func TestDivision (t *testing.T) { if i, e := Division(9.3 , 3 ); i!=3.1 || e!= nil { t.Error("Division Normal Test Failed" ) } else if i, e:= Division(1 ,0 ); i!=0 || e.Error()!="divisor can't be 0" { t.Error("Division divisor=0 test Failed" ) } else { t.Log("Division Test Failed" ) } }
执行使用go test filename_test.go
,对filename_test.go文件进行测试,如果不加文件,则运行当前目录下的所有已_test
结尾的文件。为了便于测试Division函数特意没有考虑除数为0的情况。测试结果如下。
1 2 3 4 --> gotest $ go test division_test.go --- FAIL: TestDivision (0.00s) division_test.go:19: Division divisor=0 test Failed FAIL
完善Division函数如下,再运行显示ok
。
1 2 3 4 5 6 func Division (a, b float64 ) (float64 , error) { if b == 0 { return 0 , fmt.Errorf("divisor can't be 0" ) } return a/b, nil }
性能测试 还是对于上面的Division函数,编写性能测试函数如下。
1 2 3 4 5 func BenchmarkDivision (b *testing.B) { for i:=0 ; i<b.N; i++ { Division(3 ,1 ) } }
性能测试的时候需要加一个-bench
参数,对于我们这个例子使用go test -bench='.' division_test.go
即可,执行结果如下:
1 2 3 PASS BenchmarkDivision-8 500000000 3.07 ns/op ok
中间一行的意思是执行了5亿次,每次耗时3.07纳秒。
Reflect Benchmark 测试 我们使用golang testing来做一下reflect的最简单的性能测试。
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 import ( "testing" "reflect" ) type Bench struct { A int } func Foo1 (b Bench) { _ = Bench{} } func Foo2 (x interface {}) { _ = reflect.ValueOf(x) } func BenchmarkFoo1 (b *testing.B) { var x Bench for i:=0 ; i<b.N; i++ { Foo1(x) } } func BenchmarkFoo2 (b *testing.B) { var x Bench for i := 0 ; i<b.N; i++ { Foo2() } }
运行go test -bench='.'
,结果如下
1 2 3 PASS BenchmarkFoo1-8 200000000 0.47 ns/op BenchmarkFoo2-8 3000000 36.7 ns/op
比较来看差距确实比较大啊,当然如果单纯从时间角度来看感觉还好。我们来看一下reflect的源码,试着分析一下时间都浪费在了什么地方。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func ValueOf (i interface {}) Value { if i == nil { return Value{} } escapes(i) return unpackEface(i) } func unpackEface (i interface {}) Value { e := (*emptyInterface)(unsafe.Pointer(&i)) t := e.typ if t == nil { return Value{} } f := flag(t.Kind()) if ifaceIndir(t) { f |= flagIndir } return Value{t, e.word, f} }
ValueOf
的工作是将built-in类型或者自定义类型(比如struct)转换成reflect包中的Value Struct
,看上去耗时的地方应该就是最后面的内存分配了。为了验证想法,可以做一下CPU Profiling
Golang Profiling golang提供了一套工具pprof用来做性能分析与优化,主要分为两种:memory使用情况和CPU使用情况。关于pprof的具体使用这里就不细说,后面会把他和gcvis放在一起再说一下。为了做profiling,需要在原程序插入一些代码,主要是做采集用的,代码如下,插入的代码已经标记出来了。
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 import ( "flag" "fmt" "os" "reflect" "runtime/pprof" ) type Kltao struct { Age int } func Foo (k interface {}) { _ = reflect.ValueOf(k) } var cpuprofile = flag.String("cpuprofile" , "" , "write cpu profile to file" )func main () { flag.Parse() if *cpuprofile != "" { f, err := os.Create(*cpuprofile) if err != nil { fmt.Println(err) } pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() } for i:=0 ; i<50000000 ; i++ { var k Kltao Foo(k) } }
运行。
1 2 3 4 5 $ go build reflectt.go $ ./reflectt cpuprofile=reflect.prof $ go tool pprof reflectt reflect.prof Entering interactive mode (type "help" for command ) (pprof) web
输入web之后将会通过浏览器打开各个函数的开销图,如下。 看了上图我只想说,这个真不是我想象的那么简单,这个图真是看的脑袋疼。仔细分析之后可以得到一个大概结论,reflect慢主要有两个原因:一是涉及到内存分配以后GC;二是reflect实现里面有大量的枚举,也就是for循环,比如类型之类的。
其他Benchmark Test 上面的测试太简单了,可能会有以偏概全的嫌疑,羡慕提供了一个函数调用的性能测试,profiling图我先自己研究研究。
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 47 48 49 50 51 52 53 54 55 56 57 58 package testimport ( "testing" "reflect" ) type Kltao struct { Age int } func (k Kltao) Foo () { for i := 0 ; i < 10000 ; i++ { k.Age++ } } func (k Kltao) Foo1 () { for i := 0 ; i < 10000 ; i++ { k.Age++ } } func (k Kltao) Foo2 () { for i := 0 ; i < 1000000 ; i++ { k.Age++ } } func (k Kltao) Foo3 () { for i := 0 ; i < 10000 ; i++ { k.Age++ } } func SimpleFoo (kltao Kltao) { kltao.Foo1() } func ReflectFoo (k interface {}) { v := reflect.ValueOf(k) foo := v.MethodByName("Foo1" ) foo.Call(nil ) } func BenchmarkSimpleFoo (b *testing.B) { var kltao Kltao for i := 0 ; i < b.N; i++ { SimpleFoo(kltao) } } func BenchmarkReflectFoo (b *testing.B) { var kltao Kltao for i := 0 ; i < b.N; i++ { ReflectFoo(kltao) } }
性能测试结果如下。
1 2 3 PASS BenchmarkSimpleFoo-8 500000 3206 ns/op BenchmarkReflectFoo-8 500000 3547 ns/op