性能调优

性能分析工具 pprof

Go 语言内置了强大的性能分析工具 pprof,可以帮助分析 CPU、内存、Goroutine 等性能问题。

在程序中启用 pprof

package main

import (
    "log"
    "net/http"
    _ "net/http/pprof"
)

func main() {
    // 启动 pprof HTTP 服务器
    go func() {
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()

    // 主程序逻辑
    select {}
}

访问 pprof

# 浏览器访问
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

40.2 CPU 分析

生成 CPU Profile

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
}

分析 CPU Profile

# 交互模式分析
go tool pprof cpu.prof

# 常用命令
(pprof) top10        # 查看 top 10 消耗
(pprof) list main    # 查看函数详情
(pprof) web          # 生成可视化图表

# 直接生成火焰图
go tool pprof -http=:8080 cpu.prof

CPU 分析示例

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
}

40.3 内存分析

生成内存 Profile

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
}

分析内存 Profile

# 分析内存
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("---")
}

40.4 Goroutine 分析

查看 Goroutine 状态

# 通过 HTTP 查看当前 goroutine
curl http://localhost:6060/debug/pprof/goroutine?debug=1

# 使用 pprof 分析
go tool pprof http://localhost:6060/debug/pprof/goroutine

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())
}

40.5 性能优化技巧

1. 减少内存分配

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)
    }
}

2. 使用 sync.Pool

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
    }
}

3. 避免不必要的转换

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)
}

4. 预分配切片和 Map

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
}

5. 使用值类型代替指针

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
}

40.6 使用 benchmark 对比优化

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))
    }
}

40.7 trace 工具

使用 trace

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

# 查看 trace
go tool trace trace.out

40.8 小结

本章详细介绍了 Go 语言的性能调优:

  1. pprof 工具:CPU、内存、Goroutine 分析
  2. CPU 分析:找出 CPU 热点函数
  3. 内存分析:检测内存泄漏和优化内存使用
  4. Goroutine 分析:检测 Goroutine 泄漏
  5. 优化技巧:减少分配、使用 sync.Pool、预分配
  6. trace 工具:分析程序执行流程

性能调优是一个持续的过程,需要结合具体场景进行分析和优化。在下一章中,我们将学习调试技巧。