天天看點

go| go 性能優化入門之「Go代碼重構:23倍的性能爆增」實踐

最近在整理以前攢的 go 語言學習資料 -- 可能很多人都和我一樣, 随手一個收藏, 不動手也不深入, 然後就過去了. 這次從故紙堆裡掃出來, 當然不能錯過

資料:

- blog 位址:

https://www.cnblogs.com/sunsky303/p/9296188.html

- 原作者已經提供好了代碼:

https://github.com/Deleplace/forks-golang-good-code-bad-code

學習到的知識:

- 使用 `go test` 進行 單測/壓測

- 使用 `go tool` 進行 prof/trace

- 性能問題 debug 與優化思路

## let's party

- 作者準備好了代碼

- 确定基準, 使用 cpuprof 中的 `ns/op` 作為比較基準

```sh

cd bad

➜  bad git:(master) ✗ go test -bench=. -cpuprofile cpu.prof

goos: darwin

goarch: amd64

pkg: test/bad

BenchmarkParseAdexpMessage-8       18999      63848 ns/op

PASS

ok   test/bad 2.007s

```

- bad & good 代碼對比: good 更慣用,更易讀,利用go語言的細節, 後續的修改都基于 good 代碼進行

- 檢視 trace, 檢視 CPU 使用情況

# 使用 trace 工具

go test -bench=. -trace trace.out

go tool trace trace.out # 會在預設浏覽器中打開 trace

![image](https://upload-images.jianshu.io/upload_images/567399-82aaccf554e74881.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

- trace 分析: 放大 CPU 部分 -> 數千個小的彩色計算切片 + 空閑插槽 -> 一些核心處于空閑狀态

- 首先進行競争檢測, **如果發生競争, 比性能問題更嚴重**

# 競争檢測

go test -race

- 嘗試 **不開協程**, 對應代碼:

https://github.com/Deleplace/forks-golang-good-code-bad-code/tree/nogo

```go

// 改動就一行

for _, line := range in {

// go mapLine(line, ch)

mapLine(line, ch)

}

- 使用 cpuprof, 檢視熱函數調用, 定位到瓶頸

# 1. 生成 cpuprof

go test -bench=. -cpuprofile cpu.prof

# 2. 生成 svg

go tool pprof -svg cpu.prof > cpu.svg

# 3. 使用 chrome 打開 svg 檔案即可

- 根據瓶頸進行性能優化:

https://github.com/Deleplace/forks-golang-good-code-bad-code/tree/performance

   - Fast custom trim func, to remove the space character only.

   - Use bytes.HasPrefix.

   - regexp.MustCompile is exactly what we need here.

   - Instead of regexp, use a loop: 10x speedupgap .

   - bytes.IndexByte is more appropriate here.

   - Small parseLine and findSubfields refactoring, same perf.

   - Remove startWith, call directly bytes.HasPrefix: slightly faster.

- 協程使用(排程)優化: 5k message + 20/100 協程

   - 20 協程:

https://github.com/Deleplace/forks-golang-good-code-bad-code/commit/a2dfc2a6e8397ae1a3dd6f4be19786ebb45008be

   - 100 協程:

https://github.com/Deleplace/forks-golang-good-code-bad-code/commit/26dbf25f2d01c96002a0e9ba66210a9e58ebbbbe

- 到此, 已經優化達到的效果

![image](https://upload-images.jianshu.io/upload_images/567399-0aa19ea22bfc478f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

- 還能不能再過分一點: 能, 用 `Lexer + Parser`

https://github.com/Deleplace/forks-golang-good-code-bad-code/tree/lexerparser

## 寫在最後

> 總算把一個很久很久之前的坑給填上了, 開心😺

- prof 相關: 可以定位熱點函數, 友善定位瓶頸

go test -bench=. -cpuprofile cpu.prof # 壓測, 生成 prof 檔案

go tool pprof -svg cpu.prof > cpu.svg # 使用 prof 工具, prof 轉為 svg, svg 可以使用 chrome 打開

- trace 相關: 可以檢視 cpu 使用狀态

go tool trace trace.out

- goroutine 相關

首先要區分 CPU密集型任務/IO密集型任務, 協程更适合處理 **IO密集型任務**, 減少 IO wait 導緻的 CPU 空轉, 其次協程過多會導緻協程排程的開銷, 同樣會造成性能損失

- 推薦使用 github desktop

切換分支, 檢視 commit, so easy ~