github.com/lxt1045/json@v0.0.0-20231013032136-54d6b1d6e525/json.md (about) 1 # 目标是性能最好的 Go JSON 库 2 3 4 # 1. 常用的性能判断方法 5 由于笔者能力有限,这里并不会晚上的介绍所有优化方法,仅根据自己经验简单介绍 Go 下的优化工具的一种使用方式,其他使用方式需要读者自己探索。 6 ## 1.1 pprof 工具使用: 7 参考文档:\ 8 [golang 性能优化分析工具 pprof (上) - 基础使用介绍](https://www.cnblogs.com/jiujuan/p/14588185.html)\ 9 [《Go 语言编程之旅》6.1 Go 大杀器之性能剖析 PProf(上)](https://golang2.eddycjy.com/posts/ch6/01-pprof-1/) 10 11 我们知道 Go 的 pprof 工具有三种使用方式: 12 runtime/pprof:采集程序(非 Server)的指定区块的运行数据进行分析。\ 13 net/http/pprof:基于 HTTP Server 运行,并且可以采集运行时数据进行分析。\ 14 go test:通过运行测试用例,并指定所需标识来进行采集。 15 16 这里仅介绍 go test 方式,分为这几个步骤:\ 17 1. 这种方式需要先创建一个测试函数,最好是 Benchmark 类型。 18 ```go 19 func Benchmark_Sample(b *testing.B) { 20 for i := 0; i < b.N; i++ { 21 f() 22 } 23 } 24 ``` 25 2. 执行测试,生成 cpu.prof 测试文档 26 ```sh 27 go test -benchmem -run=^$ -bench ^Benchmark_Sample$ github.com/lxt1045/json -count=1 -v -cpuprofile cpu.prof 28 ``` 29 3. 执行编译命令生成二进制 30 ```sh 31 go test -benchmem -run=^$ -bench ^Benchmark_Sample$ github.com/lxt1045/json -c -o test.bin 32 ``` 33 4. 使用 go tool 命令解析 cpu.prof 测试文档 34 ```sh 35 go tool pprof ./test.bin cpu.prof 36 ``` 37 5. 使用以下命令查看:\ 38 5.1 39 ```sh 40 web # 查看 graph 图 41 ``` 42 5.2 43 ```sh 44 top n # 查看占用排行 45 ``` 46 输出例子: 47 ``` 48 Showing nodes accounting for 9270ms, 67.91% of 13650ms total 49 Dropped 172 nodes (cum <= 68.25ms) 50 Showing top 10 nodes out of 116 51 flat flat% sum% cum cum% 52 2490ms 18.24% 18.24% 2490ms 18.24% runtime.madvise 53 1880ms 13.77% 32.01% 1880ms 13.77% runtime.pthread_cond_signal 54 970ms 7.11% 39.12% 1230ms 9.01% [test.bin] 55 920ms 6.74% 45.86% 940ms 6.89% runtime.pthread_cond_wait 56 720ms 5.27% 51.14% 2570ms 18.83% github.com/lxt1045/json.parseObj 57 640ms 4.69% 55.82% 640ms 4.69% github.com/bytedance/sonic/internal/native/avx2.__native_entry__ 58 550ms 4.03% 59.85% 740ms 5.42% github.com/lxt1045/json.(*tireTree).Get 59 530ms 3.88% 63.74% 710ms 5.20% runtime.scanobject 60 300ms 2.20% 65.93% 300ms 2.20% runtime.memmove 61 270ms 1.98% 67.91% 270ms 1.98% runtime.kevent 62 ``` 63 5.3 64 ```sh 65 list func_name # 查看函数内每行代码开销; 注意 '(' 、')'、'*' 需要转义 66 ``` 67 输出例子: 68 ```sh 69 Total: 13.65s 70 ROUTINE ======================== github.com/lxt1045/json.(*tireTree).Get in /Users/bytedance/go/src/github.com/lxt1045/json/tire_tree.go 71 550ms 740ms (flat, cum) 5.42% of Total 72 . . 282: return nil 73 . . 283: } 74 . . 284: 75 . . 285: return nil 76 . . 286:} 77 30ms 30ms 287:func (root *tireTree) Get(key string) *TagInfo { 78 10ms 10ms 288: status := &root.tree[0] 79 . . 289: // for _, c := range []byte(key) { 80 20ms 20ms 290: for i := 0; i < len(key); i++ { 81 10ms 10ms 291: c := key[i] 82 . . 292: k := c & 0x7f 83 160ms 160ms 293: next := status[k] 84 . . 294: if next.next >= 0 { 85 . . 295: i += int(next.skip) 86 10ms 10ms 296: status = &root.tree[next.next] 87 . . 297: continue 88 . . 298: } 89 10ms 10ms 299: if next.idx >= 0 { 90 40ms 40ms 300: tag := root.tags[next.idx] 91 250ms 440ms 301: if len(key) > len(tag.TagName) && key[len(tag.TagName)] == '"' && tag.TagName == key[:len(tag.TagName)] { 92 10ms 10ms 302: return tag 93 . . 303: } 94 . . 304: } 95 . . 305: return nil 96 . . 306: } 97 . . 307: 98 ``` 99 5.4 100 ```sh 101 go tool pprof -http=:8080 cpu.prof # 通过浏览器查看测试结果 102 ``` 103 执行后,通过浏览器打开 http://localhost:8080/ 链接就可以查看了。 104 105 # 2. json 库的特点和针对应的优化方案 106 3. lxt1045/json 的优化方案 107 3.1 108 3.2 109 110 我们知道 golang 的反射性能 111 112 采用了哪些优化方案 113 ## 1. 反射 114 我们知道golang 的反射性能是比较差的,只要是因为反射的时候需要生成一个逃逸的对象。 115 116 但是,因为生成新对象的时候,需要配合 GC 做内存标注,所以必须使用 117 118 ## 缓存 119 120 RCU 121 122 针对指针 通过 reflect.struct 动态生成 struct 123 124 ## 字符串搜索 125 126 ## 附 127 1. net/http/pprof:基于 HTTP Server 运行,并且可以采集运行时数据进行分析。 128 129 1.1 在main package 中加入以下代码: 130 ```go 131 //main.go 132 import ( 133 "net/http" 134 _ "net/http/pprof" 135 ) 136 func main() { 137 go func() { 138 runtime.SetBlockProfileRate(1) // 开启对阻塞操作的跟踪,block 139 runtime.SetMutexProfileFraction(1) // 开启对锁调用的跟踪,mutex 140 141 err := http.ListenAndServe(":6060", nil) 142 stdlog.Fatal(err) 143 }() 144 } 145 ``` 146 就可以通过 http://127.0.0.1:6060/debug/pprof/ url访问相关信息: 147 ```sh 148 /debug/pprof/ 149 Set debug=1 as a query parameter to export in legacy text format 150 151 152 Types of profiles available: 153 Count Profile 154 3 allocs 155 2 block 156 0 cmdline 157 10 goroutine 158 3 heap 159 0 mutex 160 0 profile 161 10 threadcreate 162 0 trace 163 full goroutine stack dump 164 Profile Descriptions: 165 166 allocs: A sampling of all past memory allocations 167 block: Stack traces that led to blocking on synchronization primitives 168 cmdline: The command line invocation of the current program 169 goroutine: Stack traces of all current goroutines. Use debug=2 as a query parameter to export in the same format as an unrecovered panic. 170 heap: A sampling of memory allocations of live objects. You can specify the gc GET parameter to run GC before taking the heap sample. 171 mutex: Stack traces of holders of contended mutexes 172 profile: CPU profile. You can specify the duration in the seconds GET parameter. After you get the profile file, use the go tool pprof command to investigate the profile. 173 threadcreate: Stack traces that led to the creation of new OS threads 174 trace: A trace of execution of the current program. You can specify the duration in the seconds GET parameter. After you get the trace file, use the go tool trace command to investigate the trace. 175 ``` 176 177 通过以下命令可以获取相关的pprof文件: 178 ```sh 179 # CPU 180 curl -o cpu.out http://127.0.0.1:6060/debug/pprof/profile?seconds=20 181 # memery 182 curl -o cpu.out http://127.0.0.1:6060/debug/pprof/allocs?seconds=30 183 # 184 curl -o cpu.out http://127.0.0.1:6060/debug/pprof/mutex?seconds=15 185 # 186 curl -o cpu.out http://127.0.0.1:6060/debug/pprof/block?seconds=15 187 ``` 188 189 其中 net/http/pprof 使用 runtime/pprof 包来进行封装,并在 http 端口上暴露出来。runtime/pprof 可以用来产生 dump 文件,再使用 Go Tool PProf 来分析这运行日志。 190 191 如果应用使用了自定义的 Mux,则需要手动注册一些路由规则: 192 ```go 193 r.HandleFunc("/debug/pprof/", pprof.Index) 194 r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 195 r.HandleFunc("/debug/pprof/profile", pprof.Profile) 196 r.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 197 r.HandleFunc("/debug/pprof/trace", pprof.Trace) 198 ``` 199 ```go 200 func RouteRegister(rg *gin.RouterGroup, prefixOptions ...string) { 201 prefix := getPrefix(prefixOptions...) 202 203 prefixRouter := rg.Group(prefix) 204 { 205 prefixRouter.GET("/", pprofHandler(pprof.Index)) 206 prefixRouter.GET("/cmdline", pprofHandler(pprof.Cmdline)) 207 prefixRouter.GET("/profile", pprofHandler(pprof.Profile)) 208 prefixRouter.POST("/symbol", pprofHandler(pprof.Symbol)) 209 prefixRouter.GET("/symbol", pprofHandler(pprof.Symbol)) 210 prefixRouter.GET("/trace", pprofHandler(pprof.Trace)) 211 prefixRouter.GET("/allocs", pprofHandler(pprof.Handler("allocs").ServeHTTP)) 212 prefixRouter.GET("/block", pprofHandler(pprof.Handler("block").ServeHTTP)) 213 prefixRouter.GET("/goroutine", pprofHandler(pprof.Handler("goroutine").ServeHTTP)) 214 prefixRouter.GET("/heap", pprofHandler(pprof.Handler("heap").ServeHTTP)) 215 prefixRouter.GET("/mutex", pprofHandler(pprof.Handler("mutex").ServeHTTP)) 216 prefixRouter.GET("/threadcreate", pprofHandler(pprof.Handler("threadcreate").ServeHTTP)) 217 } 218 } 219 ``` 220 221 其它的数据的分析和CPU、Memory基本一致。下面列一下所有的数据类型: 222 223 http://localhost:8082/debug/pprof/ :获取概况信息,即图一的信息 224 http://localhost:8082/debug/pprof/allocs : 分析内存分配 225 http://localhost:8082/debug/pprof/block : 分析堆栈跟踪导致阻塞的同步原语 226 http://localhost:8082/debug/pprof/cmdline : 分析命令行调用的程序,web下调用报错 227 http://localhost:8082/debug/pprof/goroutine : 分析当前 goroutine 的堆栈信息 228 http://localhost:8082/debug/pprof/heap : 分析当前活动对象内存分配 229 http://localhost:8082/debug/pprof/mutex : 分析堆栈跟踪竞争状态互斥锁的持有者 230 http://localhost:8082/debug/pprof/profile : 分析一定持续时间内CPU的使用情况 231 http://localhost:8082/debug/pprof/threadcreate : 分析堆栈跟踪系统新线程的创建 232 http://localhost:8082/debug/pprof/trace : 分析追踪当前程序的执行状况 233