06-Go性能优化

Go性能已很优秀,但仍有优化空间。理解runtime、内存分配、并发模型是优化关键。

性能分析工具

pprof

CPU和内存分析,定位瓶颈。

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

func main() {
    // 启动pprof HTTP服务
    go func() {
        http.ListenAndServe("localhost:6060", nil)
    }()
    
    // 程序逻辑
}

使用:

# CPU profile(采样30秒)
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30

# 内存profile
go tool pprof http://localhost:6060/debug/pprof/heap

# goroutine
go tool pprof http://localhost:6060/debug/pprof/goroutine

# 阻塞profile
go tool pprof http://localhost:6060/debug/pprof/block

# 互斥锁profile
go tool pprof http://localhost:6060/debug/pprof/mutex

# 交互式命令
(pprof) top          # 前10个耗时函数
(pprof) list funcName # 查看函数代码
(pprof) web          # 生成调用图(需graphviz)

Benchmark

func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(2, 3)
    }
}

func BenchmarkConcat(b *testing.B) {
    b.ResetTimer()  // 重置计时器(排除初始化)
    for i := 0; i < b.N; i++ {
        concat("hello", "world")
    }
}

func BenchmarkWithSetup(b *testing.B) {
    data := setupData()  // 准备数据
    b.ResetTimer()
    
    for i := 0; i < b.N; i++ {
        process(data)
    }
}
# 运行benchmark
go test -bench=. -benchmem

# 输出示例
BenchmarkAdd-8      1000000000    0.25 ns/op    0 B/op    0 allocs/op
# 8核,10亿次,每次0.25ns,0字节分配,0次分配

# 比较优化前后
go test -bench=. -benchmem > old.txt
# 修改代码
go test -bench=. -benchmem > new.txt
benchcmp old.txt new.txt

trace

分析goroutine调度、GC、系统调用。

import "runtime/trace"

func main() {
    f, _ := os.Create("trace.out")
    defer f.Close()
    
    trace.Start(f)
    defer trace.Stop()
    
    // 程序逻辑
}
# 生成trace
go run main.go

# 查看trace
go tool trace trace.out
# 浏览器打开,查看goroutine、GC、系统调用时间线

内存优化

减少内存分配

// ❌ 每次都分配
func processItems(items []int) []int {
    result := []int{}  // 零长度slice
    for _, item := range items {
        result = append(result, item*2)  // 可能多次扩容
    }
    return result
}

// ✅ 预分配容量
func processItems(items []int) []int {
    result := make([]int, 0, len(items))  // 预分配
    for _, item := range items {
        result = append(result, item*2)
    }
    return result
}

// ✅ 原地修改(无需新slice)
func processItems(items []int) {
    for i := range items {
        items[i] *= 2
    }
}

字符串拼接

// ❌ +拼接(每次都创建新字符串)
func concat(strs []string) string {
    result := ""
    for _, s := range strs {
        result += s  // O(n²)时间,大量分配
    }
    return result
}

// ✅ strings.Builder
func concat(strs []string) string {
    var builder strings.Builder
    builder.Grow(estimatedSize)  // 预分配
    for _, s := range strs {
        builder.WriteString(s)
    }
    return builder.String()
}

// ✅ bytes.Buffer(类似)
func concat(strs []string) string {
    var buf bytes.Buffer
    for _, s := range strs {
        buf.WriteString(s)
    }
    return buf.String()
}

// ✅ strings.Join(简单场景)
result := strings.Join(strs, "")

对象池(sync.Pool)

var bufferPool = sync.Pool{
    New: func() interface{} {
        return new(bytes.Buffer)
    },
}

func processData(data []byte) {
    buf := bufferPool.Get().(*bytes.Buffer)
    defer func() {
        buf.Reset()  // 重置
        bufferPool.Put(buf)  // 归还
    }()
    
    buf.Write(data)
    // 使用buf
}

避免逃逸到堆

// ❌ 逃逸到堆
func createUser(name string) *User {
    u := User{Name: name}  // 逃逸
    return &u
}

// ✅ 栈分配(值返回)
func createUser(name string) User {
    return User{Name: name}
}

// 查看逃逸分析
// go build -gcflags="-m" main.go

并发优化

goroutine池

// ❌ 无限制创建
for i := 0; i < 10000; i++ {
    go process(i)  // 创建1万个goroutine
}

// ✅ worker pool
func workerPool(jobs <-chan Job, results chan<- Result) {
    const numWorkers = 10
    var wg sync.WaitGroup
    
    for i := 0; i < numWorkers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for job := range jobs {
                results <- process(job)
            }
        }()
    }
    
    wg.Wait()
    close(results)
}

减少锁竞争

// ❌ 粗粒度锁
type Counter struct {
    mu    sync.Mutex
    count int
}

func (c *Counter) Inc() {
    c.mu.Lock()
    c.count++
    c.mu.Unlock()
}

// ✅ 原子操作(无锁)
type Counter struct {
    count int64
}

func (c *Counter) Inc() {
    atomic.AddInt64(&c.count, 1)
}

// ✅ 分段锁(高并发)
type ShardedMap struct {
    shards [16]struct {
        mu   sync.RWMutex
        data map[string]interface{}
    }
}

func (m *ShardedMap) Set(key string, value interface{}) {
    shard := &m.shards[hash(key)%16]
    shard.mu.Lock()
    shard.data[key] = value
    shard.mu.Unlock()
}

channel vs mutex

// Benchmark对比
func BenchmarkMutex(b *testing.B) {
    var mu sync.Mutex
    counter := 0
    
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            mu.Lock()
            counter++
            mu.Unlock()
        }
    })
}

func BenchmarkChannel(b *testing.B) {
    ch := make(chan int, 100)
    go func() {
        counter := 0
        for range ch {
            counter++
        }
    }()
    
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            ch <- 1
        }
    })
}

// 结果:mutex通常快2-5倍(简单场景)
// channel优势:解耦、更清晰

CPU优化

避免不必要的反射

// ❌ 反射(慢10-100倍)
func sumReflect(slice interface{}) int {
    v := reflect.ValueOf(slice)
    sum := 0
    for i := 0; i < v.Len(); i++ {
        sum += int(v.Index(i).Int())
    }
    return sum
}

// ✅ 直接类型断言
func sumDirect(slice []int) int {
    sum := 0
    for _, v := range slice {
        sum += v
    }
    return sum
}

内联优化

// 小函数自动内联
func add(a, b int) int {
    return a + b
}

// 查看内联决策
// go build -gcflags="-m=2" main.go

// 禁止内联(调试用)
//go:noinline
func noInline(a, b int) int {
    return a + b
}

循环优化

// ❌ 重复计算
for i := 0; i < len(slice); i++ {
    // len(slice)每次都调用
}

// ✅ 缓存长度
n := len(slice)
for i := 0; i < n; i++ {
    // ...
}

// ✅ range(编译器优化)
for i, v := range slice {
    // ...
}

I/O优化

缓冲I/O

// ❌ 无缓冲
file, _ := os.Open("file.txt")
scanner := bufio.NewScanner(file)
for scanner.Scan() {
    line := scanner.Text()
    // 处理每行
}

// ✅ 指定缓冲大小
file, _ := os.Open("file.txt")
reader := bufio.NewReaderSize(file, 1024*1024)  // 1MB缓冲
scanner := bufio.NewScanner(reader)

批量操作

// ❌ 逐条插入
for _, item := range items {
    db.Exec("INSERT INTO table VALUES (?)", item)
}

// ✅ 批量插入
tx, _ := db.Begin()
stmt, _ := tx.Prepare("INSERT INTO table VALUES (?)")
for _, item := range items {
    stmt.Exec(item)
}
tx.Commit()

编译优化

编译选项

# 默认优化
go build

# 禁用优化(调试)
go build -gcflags="-N -l"

# 内联级别
go build -gcflags="-l=4"  # 更激进的内联

# 逃逸分析详情
go build -gcflags="-m -m"

# 减小二进制大小
go build -ldflags="-s -w"
# -s: 去除符号表
# -w: 去除调试信息

代码生成

//go:generate stringer -type=Status
type Status int

const (
    Pending Status = iota
    Running
    Completed
)

// 生成String()方法
// go generate

GC优化

减少GC压力

// ❌ 频繁分配小对象
func process() {
    for i := 0; i < 1000000; i++ {
        obj := &MyStruct{}  // 100万次分配
        use(obj)
    }
}

// ✅ 复用对象
var pool = sync.Pool{
    New: func() interface{} {
        return &MyStruct{}
    },
}

func process() {
    for i := 0; i < 1000000; i++ {
        obj := pool.Get().(*MyStruct)
        use(obj)
        pool.Put(obj)
    }
}

调整GC参数

import "runtime/debug"

// 设置GC百分比(默认100)
debug.SetGCPercent(200)  // 降低GC频率,增加内存占用

// 手动触发GC(少用)
runtime.GC()

// 查看GC统计
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("Alloc = %v MB", m.Alloc/1024/1024)
fmt.Printf("TotalAlloc = %v MB", m.TotalAlloc/1024/1024)
fmt.Printf("NumGC = %v\n", m.NumGC)

JSON优化

标准库 vs 第三方

// 标准库encoding/json
import "encoding/json"

// jsoniter(兼容,快2-3倍)
import jsoniter "github.com/json-iterator/go"
var json = jsoniter.ConfigCompatibleWithStandardLibrary

// easyjson(代码生成,快5-10倍)
//go:generate easyjson -all user.go
type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}
// 自动生成MarshalJSON/UnmarshalJSON

// sonic(字节跳动,AVX2加速)
import "github.com/bytedance/sonic"

流式解析

// ❌ 一次性读取大JSON
var data LargeData
json.Unmarshal(largeJSON, &data)

// ✅ 流式解析
decoder := json.NewDecoder(reader)
for {
    var item Item
    if err := decoder.Decode(&item); err == io.EOF {
        break
    }
    process(item)
}

数据库优化

连接池

db, _ := sql.Open("mysql", dsn)

// 配置连接池
db.SetMaxOpenConns(25)              // 最大连接数
db.SetMaxIdleConns(5)               // 最大空闲连接
db.SetConnMaxLifetime(5*time.Minute)// 连接最大生命周期
db.SetConnMaxIdleTime(10*time.Minute)// 空闲连接最大时间

预编译语句

// ❌ 每次都Prepare
for _, user := range users {
    db.Exec("INSERT INTO users (name) VALUES (?)", user.Name)
}

// ✅ 复用Prepare
stmt, _ := db.Prepare("INSERT INTO users (name) VALUES (?)")
defer stmt.Close()

for _, user := range users {
    stmt.Exec(user.Name)
}

网络优化

HTTP连接池

var client = &http.Client{
    Timeout: 10 * time.Second,
    Transport: &http.Transport{
        MaxIdleConns:        100,
        MaxIdleConnsPerHost: 10,
        IdleConnTimeout:     90 * time.Second,
        // 启用HTTP/2
        ForceAttemptHTTP2: true,
    },
}

gRPC连接复用

// 复用连接
conn, _ := grpc.Dial(address, grpc.WithInsecure())
defer conn.Close()

client := pb.NewServiceClient(conn)

// 多次调用复用同一连接
for i := 0; i < 1000; i++ {
    client.Call(ctx, req)
}

实战案例

优化前

func processUsers(userIDs []int) ([]User, error) {
    var users []User
    
    for _, id := range userIDs {
        // 逐个查询数据库
        var user User
        db.QueryRow("SELECT * FROM users WHERE id = ?", id).Scan(&user)
        users = append(users, user)
    }
    
    return users, nil
}

优化后

func processUsers(userIDs []int) ([]User, error) {
    // 1. 批量查询
    placeholders := strings.Repeat("?,", len(userIDs))
    placeholders = placeholders[:len(placeholders)-1]
    
    query := fmt.Sprintf("SELECT * FROM users WHERE id IN (%s)", placeholders)
    
    args := make([]interface{}, len(userIDs))
    for i, id := range userIDs {
        args[i] = id
    }
    
    rows, err := db.Query(query, args...)
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    
    // 2. 预分配容量
    users := make([]User, 0, len(userIDs))
    
    for rows.Next() {
        var user User
        rows.Scan(&user)
        users = append(users, user)
    }
    
    return users, nil
}

性能提升:10-100倍(取决于网络延迟)

性能清单

内存:

  • 预分配slice容量

  • 使用sync.Pool复用对象

  • 字符串拼接用strings.Builder

  • 减少堆分配(避免逃逸)

  • 大对象传指针,小对象传值

并发:

  • 限制goroutine数量(worker pool)

  • 简单计数用atomic

  • 读多写少用sync.RWMutex

  • 减少锁粒度(分段锁)

CPU:

  • 避免反射(热路径)

  • 缓存重复计算

  • 批量处理代替逐个

I/O:

  • 缓冲I/O

  • 批量数据库操作

  • 连接池复用

  • HTTP keep-alive

其他:

  • 用benchmark验证

  • pprof定位瓶颈

  • 选择快速JSON库

  • 启用编译优化

核心: 先测量,后优化。过早优化是万恶之源。