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