前言
这阵子用 Golang 实现了一个类似于 python in
语法的小函数,项目 gox 的 benchmark 测试的结果如下
1 2 3 4 5
| BenchmarkOriIn-4 500000000 3.22 ns/op BenchmarkMyIn-4 10000000 175 ns/op BenchmarkOriMapIn-4 100000000 16.3 ns/op BenchmarkMyMapIn-4 2000000 784 ns/op ok github.com/lycheng/gox/container 7.939s
|
其中,OriIn 是顺序查询 slice 和 array 的元素是否存在,OriMapIn 是原生的语法去判断 key 是否存在。上述的结果可以看到相当巨大的性能差异。
我实现的 In 的函数在处理 Map 的时候是通过遍历 keys 来查询的,因此每次的类型判断乘上数据量不仅仅是一个跟 N 有关的线性增长。
profile
使用 Golang 的 runtime/pprof
包来检查相应的 CPU 消耗,这里不考虑内存消耗的问题。
profile 程序
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
| package main
import ( "flag" "fmt" "log" "os" "runtime/pprof"
"github.com/lycheng/gox/container" )
var cpuprofile = flag.String("cpuprofile", "gox.prof", "write cpu profile to file")
var l = flag.Int("len", 100000000, "find item in N sequence") var ori = flag.Bool("ori", false, "use ori array func")
func oriFind(item int, items []int) bool { for _, i := range items { if i == item { return true } } return false }
func main() { flag.Parse() if *cpuprofile != "" { f, err := os.Create(*cpuprofile) if err != nil { log.Fatal(err) } pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() }
items := []int{} for i := 0; i < *l; i++ { items = append(items, i) } item := items[len(items)-1]
if *ori { fmt.Println(oriFind(item, items)) } else { fmt.Println(container.In(item, items)) } }
|
应用如下
1 2 3 4 5 6 7 8 9
| > go build profile.go
# 简单的数组遍历 > ./profile -ori true true
# 使用 reflect > ./profile true
|
相对不用 reflect
只是简单的整型数组的查找,top10 的消耗如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| > go tool pprof profile gox.prof Entering interactive mode (type "help" for commands) (pprof) top10 3060ms of 3060ms total ( 100 Dropped 6 nodes (cum <= 15.30ms) Showing top 10 nodes out of 27 (cum >= 20ms) flat flat 810ms 26.47 760ms 24.84 630ms 20.59 340ms 11.11 220ms 7.19 200ms 6.54 80ms 2.61 20ms 0.65 0 0 0 0
|
gox 的版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| > go tool pprof profile gox.prof Entering interactive mode (type "help" for commands) (pprof) top10 5100ms of 5260ms total (96.96 Dropped 15 nodes (cum <= 26.30ms) Showing top 10 nodes out of 29 (cum >= 200ms) flat flat 1010ms 19.20 780ms 14.83 590ms 11.22 540ms 10.27 530ms 10.08 500ms 9.51 480ms 9.13 270ms 5.13 200ms 3.80 200ms 3.80
|
两种代码的执行时间只是 5s 和 3s 的区别,而 container.inArray
在这里面就占用了 2310ms,几乎花了一半的时间在类型判断和比较上面。
gox
这东西可能在数据量较小的情况下可以使用。但在我平时工作的应用场景里面,很少用到这种异构的数组。每个元素去判断类型消耗实在是太大了啊。
总结
reflect
这东西感觉还是少用点会比较好,除非用来编写奇怪动态数据,例如根据 key 修改 struct 的某些数据。文章后面的参考中有较好的第三方库。
而在我编写的过程中,也很容易出现各种 panic 的情况,例如需要覆盖各种可能的数据类型,这种情况不好处理,万一有所遗漏则就是 bug 了。
参考
- profiling in golang
- Golang 中动态修改 struct 的库