自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Go 高性能 - 打印調(diào)用堆棧

開(kāi)發(fā) 前端
本小節(jié)介紹了兩種獲取堆棧信息的方法,并通過(guò)基準(zhǔn)測(cè)試來(lái)分析兩種方法的性能差異,讀者可以在此基礎(chǔ)上封裝自己的高性能組件類庫(kù)。

概述

在工程代碼中需要在異常場(chǎng)景打印相應(yīng)的日志,記錄重要的上下文信息。如果遇到 panic 或 error 的情況, 這時(shí)候就需要詳細(xì)的 堆棧信息 作為輔助來(lái)排查問(wèn)題,本小節(jié)就來(lái)介紹兩種常見(jiàn)的獲取 堆棧信息 方法, 然后對(duì)兩種方法進(jìn)行基準(zhǔn)測(cè)試,最后使用測(cè)試的結(jié)果進(jìn)行性能對(duì)比并分析差異。

runtime.Stack

通過(guò)標(biāo)準(zhǔn)庫(kù)提供的 runtime.Stack 相關(guān) API 來(lái)獲取。

示例

package main

import (
"fmt"
"runtime"
)

func main() {
buf := make([]byte, 1024)
n := runtime.Stack(buf, true)

fmt.Printf("%s\n", buf[:n])
}
$ go run main.go

# 輸出如下 (你的輸出代碼路徑應(yīng)該和這里的不一樣)
goroutine 1 [running]:
main.main()
/home/codes/go-high-performance/main.go:10 +0x45
...

測(cè)試代碼如下

package performance

import (
"runtime"
"testing"
)

func Benchmark_StackDump(b *testing.B) {
for i := 0; i < b.N; i++ {
buf := make([]byte, 1024)
n := runtime.Stack(buf, true)

_ = buf[:n]
}
}

運(yùn)行測(cè)試,并將基準(zhǔn)測(cè)試結(jié)果寫(xiě)入文件:

# 運(yùn)行 1000 次,統(tǒng)計(jì)內(nèi)存分配
$ go test -run='^$' -bench=. -count=1 -benchtime=1000x -benchmem > slow.txt

runtime.Caller

通過(guò)標(biāo)準(zhǔn)庫(kù)提供的 runtime.Caller 相關(guān) API 來(lái)獲取。

示例

package main

import (
"fmt"
"runtime"
)

func main() {
for i := 0; ; i++ {
if _, file, line, ok := runtime.Caller(i); ok {
fmt.Printf("file: %s, line: %d\n", file, line)
} else {
break
}
}
}
$ go run main.go

# 輸出如下 (你的輸出代碼路徑應(yīng)該和這里的不一樣)
file: /home/codes/go-high-performance/main.go, line: 10
file: /usr/local/go/src/runtime/proc.go, line: 250
file: /usr/local/go/src/runtime/asm_amd64.s, line: 1594
...

從輸出的結(jié)果中可以看到,runtime.Caller 的返回值包含了 文件名稱 和 行號(hào),但是相比 runtime.Stack 的輸出而言, 缺少了 goroutine 和 調(diào)用方法 字段,我們可以通過(guò) runtime.Callers 配合 runtime.CallersFrames 輸出和 runtime.Stack 一樣的結(jié)果。

package main

import (
"fmt"
"runtime"
"strconv"
"strings"
)

func main() {
pcs := make([]uintptr, 16)
n := runtime.Callers(0, pcs)

frames := runtime.CallersFrames(pcs[:n])

var sb strings.Builder
for {
frame, more := frames.Next()

sb.WriteString(frame.Function)
sb.WriteByte('\n')
sb.WriteByte('\t')
sb.WriteString(frame.File)
sb.WriteByte(':')
sb.WriteString(strconv.Itoa(frame.Line))
sb.WriteByte('\n')

if !more {
break
}
}

fmt.Println(sb.String())
}
$ go run main.go

# 輸出如下 (你的輸出代碼路徑應(yīng)該和這里的不一樣)
runtime.Callers
/usr/local/go/src/runtime/extern.go:247
main.main
/home/codes/go-high-performance/main.go:12
runtime.main
/usr/local/go/src/runtime/proc.go:250
runtime.goexit
/usr/local/go/src/runtime/asm_amd64.s:1594
...

測(cè)試代碼

package performance

import (
"runtime"
"strconv"
"strings"
"testing"
)

func stackDump() string {
pcs := make([]uintptr, 16)
n := runtime.Callers(0, pcs)

frames := runtime.CallersFrames(pcs[:n])

var buffer strings.Builder
for {
frame, more := frames.Next()

buffer.WriteString(frame.Function)
buffer.WriteByte('\n')
buffer.WriteByte('\t')
buffer.WriteString(frame.File)
buffer.WriteByte(':')
buffer.WriteString(strconv.Itoa(frame.Line))
buffer.WriteByte('\n')

if !more {
break
}
}

return buffer.String()
}

func Benchmark_StackDump(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = stackDump()
}
}

運(yùn)行測(cè)試,并將基準(zhǔn)測(cè)試結(jié)果寫(xiě)入文件:

# 運(yùn)行 1000 次,統(tǒng)計(jì)內(nèi)存分配
$ go test -run='^$' -bench=. -count=1 -benchtime=1000x -benchmem > fast.txt

使用 benchstat 比較差異

$ benchstat -alpha=100 fast.txt slow.txt 

# 輸出如下
name old time/op new time/op delta
_StackDump-8 2.28μs ± 0% 68.89μs ± 0% +2926.85% (p=1.000 n=1+1)

name old alloc/op new alloc/op delta
_StackDump-8 1.36kB ± 0% 1.02kB ± 0% -24.71% (p=1.000 n=1+1)

name old allocs/op new allocs/op delta
_StackDump-8 12.0 ± 0% 1.0 ± 0% -91.67% (p=1.000 n=1+1)

輸出的結(jié)果分為了三行,分別對(duì)應(yīng)基準(zhǔn)測(cè)試期間的: 運(yùn)行時(shí)間、內(nèi)存分配總量、內(nèi)存分配次數(shù),可以看到:

  • ? 運(yùn)行時(shí)間: runtime.Callers 比 runtime.Stack 提升了將近 30 倍
  • ? 內(nèi)存分配總量: 兩者差不多
  • ? 內(nèi)存分配次數(shù): runtime.Callers 比 runtime.Stack 降低了將近 10 倍,當(dāng)然筆者的測(cè)試代碼也需要再優(yōu)化下

性能分析

最根本的差異點(diǎn)在于 runtime.Stack 會(huì)觸發(fā) STW 操作。

小結(jié)

本小節(jié)介紹了兩種獲取堆棧信息的方法,并通過(guò)基準(zhǔn)測(cè)試來(lái)分析兩種方法的性能差異,讀者可以在此基礎(chǔ)上封裝自己的高性能組件類庫(kù)。


責(zé)任編輯:武曉燕 來(lái)源: 真沒(méi)什么深度
相關(guān)推薦

2024-12-25 14:03:03

2022-03-21 14:13:22

Go語(yǔ)言編程

2021-08-13 09:06:52

Go高性能優(yōu)化

2021-05-27 10:02:57

Go緩存數(shù)據(jù)

2025-01-13 13:00:00

Go網(wǎng)絡(luò)框架nbio

2025-02-05 12:09:12

2023-12-26 00:58:53

Web應(yīng)用Go語(yǔ)言

2024-04-28 10:17:30

gnetGo語(yǔ)言

2023-12-01 07:06:14

Go命令行性能

2023-12-14 08:01:08

事件管理器Go

2023-11-01 10:58:31

系統(tǒng)調(diào)用高性能網(wǎng)絡(luò)編程Linux

2023-05-18 08:47:42

2024-07-30 09:02:15

2019-03-01 11:03:22

Lustre高性能計(jì)算

2017-11-28 17:14:16

華為云

2017-08-07 21:10:55

MySQLUbuntusysbench

2024-04-25 10:09:02

2020-01-07 16:16:57

Kafka開(kāi)源消息系統(tǒng)

2023-09-22 11:48:37

2017-07-07 16:36:28

BIOIO模型 NIO
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)