Go 语言内置了强大的性能分析工具 pprof,可以帮助分析 CPU、内存、Goroutine 等性能问题。
package main
import (
"log"
"net/http"
_ "net/http/pprof"
)
func main() {
// 启动 pprof HTTP 服务器
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
// 主程序逻辑
select {}
}
# 浏览器访问
http://localhost:6060/debug/pprof/
# 使用 go tool pprof
go tool pprof http://localhost:6060/debug/pprof/profile
go tool pprof http://localhost:6060/debug/pprof/heap
go tool pprof http://localhost:6060/debug/pprof/goroutine
package main
import (
"os"
"runtime/pprof"
"time"
)
func main() {
// 创建 CPU profile 文件
f, _ := os.Create("cpu.prof")
defer f.Close()
// 开始 CPU 分析
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
// 运行程序
for i := 0; i < 1000000; i++ {
calculate(i)
}
}
func calculate(n int) int {
result := 0
for i := 0; i < n; i++ {
result += i
}
return result
}
# 交互模式分析
go tool pprof cpu.prof
# 常用命令
(pprof) top10 # 查看 top 10 消耗
(pprof) list main # 查看函数详情
(pprof) web # 生成可视化图表
# 直接生成火焰图
go tool pprof -http=:8080 cpu.prof
package main
import (
"fmt"
"math/rand"
"os"
"runtime/pprof"
)
func main() {
f, _ := os.Create("cpu.prof")
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
data := make([]int, 1000000)
for i := range data {
data[i] = rand.Intn(1000000)
}
result := slowSort(data)
fmt.Println(len(result))
}
func slowSort(data []int) []int {
result := make([]int, len(data))
copy(result, data)
for i := 0; i < len(result); i++ {
for j := i + 1; j < len(result); j++ {
if result[i] > result[j] {
result[i], result[j] = result[j], result[i]
}
}
}
return result
}
package main
import (
"os"
"runtime/pprof"
)
func main() {
// 运行程序
data := allocateMemory()
// 生成内存 profile
f, _ := os.Create("mem.prof")
defer f.Close()
pprof.WriteHeapProfile(f)
_ = data
}
func allocateMemory() [][]byte {
var result [][]byte
for i := 0; i < 1000; i++ {
buf := make([]byte, 1024*1024) // 1MB
result = append(result, buf)
}
return result
}
# 分析内存
go tool pprof mem.prof
# 常用命令
(pprof) top
(pprof) list allocateMemory
(pprof) web
# 查看分配的对象数量
go tool pprof -alloc_objects mem.prof
# 查看分配的空间大小
go tool pprof -alloc_space mem.prof
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
printMemStats()
for i := 0; i < 10; i++ {
leakMemory()
runtime.GC()
printMemStats()
}
}
func leakMemory() {
data := make([]byte, 10*1024*1024) // 10MB
_ = data
}
func printMemStats() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("Alloc = %v MiB\n", m.Alloc/1024/1024)
fmt.Printf("TotalAlloc = %v MiB\n", m.TotalAlloc/1024/1024)
fmt.Printf("Sys = %v MiB\n", m.Sys/1024/1024)
fmt.Printf("NumGC = %v\n", m.NumGC)
fmt.Println("---")
}
# 通过 HTTP 查看当前 goroutine
curl http://localhost:6060/debug/pprof/goroutine?debug=1
# 使用 pprof 分析
go tool pprof http://localhost:6060/debug/pprof/goroutine
package main
import (
"fmt"
"net/http"
_ "net/http/pprof"
"runtime"
"time"
)
func main() {
go func() {
http.ListenAndServe("localhost:6060", nil)
}()
for i := 0; i < 100; i++ {
go leakyFunction()
}
time.Sleep(10 * time.Second)
printGoroutineCount()
}
func leakyFunction() {
ch := make(chan int)
<-ch // 永远阻塞
}
func printGoroutineCount() {
fmt.Printf("Goroutine 数量: %d\n", runtime.NumGoroutine())
}
package main
import (
"strings"
"testing"
)
// 不好的做法:频繁分配
func concatBad(parts []string) string {
var result string
for _, part := range parts {
result += part
}
return result
}
// 好的做法:使用 strings.Builder
func concatGood(parts []string) string {
var builder strings.Builder
for _, part := range parts {
builder.WriteString(part)
}
return builder.String()
}
// 更好的做法:预分配大小
func concatBetter(parts []string, estimatedSize int) string {
var builder strings.Builder
builder.Grow(estimatedSize)
for _, part := range parts {
builder.WriteString(part)
}
return builder.String()
}
func BenchmarkConcatBad(b *testing.B) {
parts := []string{"a", "b", "c", "d", "e"}
for i := 0; i < b.N; i++ {
concatBad(parts)
}
}
func BenchmarkConcatGood(b *testing.B) {
parts := []string{"a", "b", "c", "d", "e"}
for i := 0; i < b.N; i++ {
concatGood(parts)
}
}
package main
import (
"sync"
)
var bufferPool = sync.Pool{
New: func() interface{} {
return make([]byte, 1024)
},
}
func processWithPool() {
buf := bufferPool.Get().([]byte)
defer bufferPool.Put(buf)
// 使用 buf 进行处理
for i := 0; i < len(buf); i++ {
buf[i] = 0
}
}
package main
import (
"strconv"
)
// 不好的做法
func itoaBad(n int) string {
return strconv.Itoa(n)
}
// 如果只需要 []byte,避免转换为 string
func itoaBytes(n int) []byte {
var buf [20]byte
return strconv.AppendInt(buf[:0], int64(n), 10)
}
package main
// 不好的做法
func makeSliceBad(n int) []int {
var result []int
for i := 0; i < n; i++ {
result = append(result, i)
}
return result
}
// 好的做法
func makeSliceGood(n int) []int {
result := make([]int, 0, n)
for i := 0; i < n; i++ {
result = append(result, i)
}
return result
}
// Map 预分配
func makeMapGood(n int) map[int]string {
result := make(map[int]string, n)
for i := 0; i < n; i++ {
result[i] = "value"
}
return result
}
package main
// 对于小结构体,使用值类型更高效
type Point struct {
X, Y int
}
// 值传递
func processValue(p Point) int {
return p.X + p.Y
}
// 指针传递(对于小结构体不推荐)
func processPointer(p *Point) int {
return p.X + p.Y
}
package main
import (
"sort"
"testing"
)
func BenchmarkSortInt(b *testing.B) {
data := make([]int, 10000)
for i := range data {
data[i] = 10000 - i
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
tmp := make([]int, len(data))
copy(tmp, data)
sort.Ints(tmp)
}
}
func BenchmarkSortIntSlice(b *testing.B) {
data := make([]int, 10000)
for i := range data {
data[i] = 10000 - i
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
tmp := make([]int, len(data))
copy(tmp, data)
sort.Sort(sort.IntSlice(tmp))
}
}
package main
import (
"os"
"runtime/trace"
)
func main() {
f, _ := os.Create("trace.out")
defer f.Close()
trace.Start(f)
defer trace.Stop()
// 运行程序
for i := 0; i < 1000; i++ {
work()
}
}
func work() {
sum := 0
for i := 0; i < 1000; i++ {
sum += i
}
}
# 查看 trace
go tool trace trace.out
本章详细介绍了 Go 语言的性能调优:
性能调优是一个持续的过程,需要结合具体场景进行分析和优化。在下一章中,我们将学习调试技巧。