Go语言快速入门Go语言快速入门
首页
基础篇
进阶篇
高阶篇
实战篇
Go官方网站
编程指南
首页
基础篇
进阶篇
高阶篇
实战篇
Go官方网站
编程指南
  • 高阶篇

    • 🎯 高阶篇
    • Context
    • 反射
    • 泛型
    • 性能优化
    • 最佳实践

性能优化

Go本身很快,但要写出高性能代码,需要了解一些优化技巧。我刚开始写Go的时候,完全不关心性能问题,只要功能能实现就行。但在生产环境踩了几次坑之后,才明白性能优化的重要性。记住,过早优化是万恶之源,但也不能完全不考虑性能。

性能分析工具

pprof - CPU分析

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

func main() {
    go func() {
        http.ListenAndServe(":6060", nil)
    }()
    
    // 你的应用代码
}

访问 http://localhost:6060/debug/pprof/ 查看分析数据。

# 生成CPU分析
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30

# 常用命令
(pprof) top10       # 最耗CPU的10个函数
(pprof) web         # 生成SVG图(需要graphviz)
(pprof) list main   # 查看main函数的每行开销

基准测试分析

# 运行基准测试并生成CPU分析
go test -bench=. -cpuprofile=cpu.prof

# 分析
go tool pprof cpu.prof

内存分析

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

# 查看内存分配
(pprof) top
(pprof) list MyFunction

内存优化

减少内存分配

// ❌ 频繁分配
func concat(strs []string) string {
    result := ""
    for _, s := range strs {
        result += s  // 每次都分配新字符串
    }
    return result
}

// ✅ 使用strings.Builder
func concat(strs []string) string {
    var builder strings.Builder
    for _, s := range strs {
        builder.WriteString(s)
    }
    return builder.String()
}

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

切片预分配

// ❌ 动态扩容
func process(items []int) []int {
    result := []int{}  // 初始容量0
    for _, item := range items {
        result = append(result, item*2)
    }
    return result
}

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

// ✅✅ 直接赋值
func process(items []int) []int {
    result := make([]int, len(items))
    for i, item := range items {
        result[i] = item * 2
    }
    return result
}

sync.Pool 复用对象

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

func getBuffer() *bytes.Buffer {
    return bufferPool.Get().(*bytes.Buffer)
}

func putBuffer(buf *bytes.Buffer) {
    buf.Reset()
    bufferPool.Put(buf)
}

func process(data []byte) string {
    buf := getBuffer()
    defer putBuffer(buf)
    
    buf.Write(data)
    // 处理...
    return buf.String()
}

避免大对象在栈上

// 大对象会分配到堆上,但可以减少不必要的复制
type BigStruct struct {
    data [1024 * 1024]byte  // 1MB
}

// ❌ 值传递会复制
func process(b BigStruct) {}

// ✅ 指针传递
func process(b *BigStruct) {}

并发优化

减少锁竞争

// ❌ 全局锁
type Counter struct {
    mu    sync.Mutex
    count int
}

// ✅ 分片锁
type ShardedCounter struct {
    shards [256]struct {
        mu    sync.Mutex
        count int
    }
}

func (c *ShardedCounter) Inc(key string) {
    shard := fnv32(key) % 256
    c.shards[shard].mu.Lock()
    c.shards[shard].count++
    c.shards[shard].mu.Unlock()
}

使用原子操作

// ❌ 使用锁
type Counter struct {
    mu    sync.Mutex
    count int64
}

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

func (c *Counter) Value() int64 {
    return atomic.LoadInt64(&c.count)
}

无锁数据结构

// 使用channel代替锁
type Counter struct {
    inc chan struct{}
    get chan int64
}

func NewCounter() *Counter {
    c := &Counter{
        inc: make(chan struct{}),
        get: make(chan int64),
    }
    go c.run()
    return c
}

func (c *Counter) run() {
    var count int64
    for {
        select {
        case <-c.inc:
            count++
        case c.get <- count:
        }
    }
}

I/O优化

使用缓冲I/O

// ❌ 无缓冲
file, _ := os.Open("data.txt")
reader := file

// ✅ 有缓冲
file, _ := os.Open("data.txt")
reader := bufio.NewReader(file)

// 写入同理
writer := bufio.NewWriter(file)
defer writer.Flush()

使用io.Copy

// ❌ 手动复制
buf := make([]byte, 1024)
for {
    n, err := src.Read(buf)
    if err != nil {
        break
    }
    dst.Write(buf[:n])
}

// ✅ 使用io.Copy(内部有优化)
io.Copy(dst, src)

编译优化

逃逸分析

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

// 减少逃逸
// ❌ 返回指针导致逃逸
func newInt() *int {
    x := 42
    return &x  // x逃逸到堆
}

// ✅ 传入指针
func setInt(x *int) {
    *x = 42
}

内联优化

// 小函数会被内联
//go:noinline  // 禁止内联(仅用于测试)
func add(a, b int) int {
    return a + b
}

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

常见优化技巧

1. 字符串和[]byte转换

// ❌ 标准转换会复制
s := string(bytes)
b := []byte(str)

// ✅ 零拷贝转换(不安全,慎用)
func StringToBytes(s string) []byte {
    return unsafe.Slice(unsafe.StringData(s), len(s))
}

func BytesToString(b []byte) string {
    return unsafe.String(unsafe.SliceData(b), len(b))
}

2. 避免不必要的类型转换

// ❌ 频繁转换
for _, v := range nums {
    sum += float64(v)  // 每次都转换
}

// ✅ 使用相同类型
var sum int
for _, v := range nums {
    sum += v
}
result := float64(sum) / float64(len(nums))

3. 使用值接收者避免解引用

// 小结构体用值接收者
type Point struct {
    X, Y int
}

func (p Point) Distance() float64 {
    return math.Sqrt(float64(p.X*p.X + p.Y*p.Y))
}

4. Map优化

// 预分配容量
m := make(map[string]int, 1000)

// 使用整数key比字符串快
// map[int]value 比 map[string]value 快

// 避免在热点路径查询两次
// ❌
if _, ok := m[key]; ok {
    v := m[key]
}

// ✅
if v, ok := m[key]; ok {
    // use v
}

基准测试示例

func BenchmarkConcat(b *testing.B) {
    strs := []string{"hello", " ", "world", "!"}
    
    b.Run("Plus", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            result := ""
            for _, s := range strs {
                result += s
            }
        }
    })
    
    b.Run("Builder", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            var builder strings.Builder
            for _, s := range strs {
                builder.WriteString(s)
            }
            _ = builder.String()
        }
    })
    
    b.Run("Join", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            _ = strings.Join(strs, "")
        }
    })
}

优化原则

1. 先测量,后优化

// 不要猜测,用数据说话
go test -bench=. -benchmem
go tool pprof cpu.prof

2. 优化热点

找到20%消耗80%资源的代码,优先优化。

3. 保持代码可读

// ❌ 过度优化,难以维护
func process(x int) int {
    return x<<1 + x  // 3 * x
}

// ✅ 清晰的代码
func process(x int) int {
    return 3 * x  // 编译器会优化
}

4. 避免过早优化

"过早优化是万恶之源" — Donald Knuth

先写正确的代码,再优化性能。

练习

  1. 使用pprof分析一个CPU密集型程序
  2. 优化一个有大量字符串拼接的函数
  3. 使用sync.Pool优化一个频繁分配对象的场景
参考答案
// 2. 字符串拼接优化
func BuildReport(items []Item) string {
    // 预估大小
    size := len(items) * 100  // 假设每项约100字节
    
    var sb strings.Builder
    sb.Grow(size)
    
    for _, item := range items {
        sb.WriteString(item.Name)
        sb.WriteByte(':')
        sb.WriteString(strconv.Itoa(item.Value))
        sb.WriteByte('\n')
    }
    
    return sb.String()
}

// 3. sync.Pool使用
type Parser struct {
    buffer []byte
}

var parserPool = sync.Pool{
    New: func() interface{} {
        return &Parser{
            buffer: make([]byte, 0, 1024),
        }
    },
}

func Parse(data []byte) Result {
    p := parserPool.Get().(*Parser)
    defer func() {
        p.buffer = p.buffer[:0]
        parserPool.Put(p)
    }()
    
    // 使用p.buffer处理数据
    return result
}

性能优化是个大话题,最后一节学习最佳实践!

最近更新: 2025/12/27 13:26
Contributors: 王长安
Prev
泛型
Next
最佳实践