Skip to content

Latest commit

 

History

History
659 lines (505 loc) · 12.3 KB

File metadata and controls

659 lines (505 loc) · 12.3 KB

11.2 CPU 优化

📍 导航返回目录 | 上一节:方法论 | 下一节:IO优化


CPU 瓶颈识别

如何判断是 CPU 瓶颈?

# 查看 CPU 使用率
top
htop

# 查看 CPU 详细信息
mpstat -P ALL 1

# 查看进程 CPU 使用
ps aux --sort=-%cpu | head -10

CPU 瓶颈特征

  • CPU 使用率持续 > 80%
  • 用户态 CPU (us) 占比高
  • 负载 (load average) 持续 > CPU 核心数

优化策略

1. 算法优化

核心原则:降低时间复杂度

案例:查找重复元素

// ❌ O(n²) 暴力查找
func findDuplicatesSlow(nums []int) []int {
    var result []int
    for i := 0; i < len(nums); i++ {
        for j := i + 1; j < len(nums); j++ {
            if nums[i] == nums[j] {
                result = append(result, nums[i])
                break
            }
        }
    }
    return result
}

// ✅ O(n) 哈希表优化
func findDuplicatesFast(nums []int) []int {
    seen := make(map[int]bool)
    var result []int
    
    for _, num := range nums {
        if seen[num] {
            result = append(result, num)
        }
        seen[num] = true
    }
    return result
}

性能对比

  • 输入 10000 个元素
  • O(n²) 版本:~500ms
  • O(n) 版本:~5ms
  • 性能提升:100x

2. 数据结构优化

案例:LRU 缓存实现

import "container/list"

// ✅ 高效 LRU:哈希表 + 双向链表,O(1) 读写
type LRUCache struct {
    capacity int
    cache    map[int]*list.Element
    lruList  *list.List
}

type entry struct {
    key   int
    value int
}

func NewLRUCache(capacity int) *LRUCache {
    return &LRUCache{
        capacity: capacity,
        cache:    make(map[int]*list.Element),
        lruList:  list.New(),
    }
}

func (c *LRUCache) Get(key int) int {
    if elem, ok := c.cache[key]; ok {
        c.lruList.MoveToFront(elem) // O(1)
        return elem.Value.(*entry).value
    }
    return -1
}

func (c *LRUCache) Put(key, value int) {
    if elem, ok := c.cache[key]; ok {
        c.lruList.MoveToFront(elem)
        elem.Value.(*entry).value = value
        return
    }
    
    if c.lruList.Len() == c.capacity {
        back := c.lruList.Back()
        if back != nil {
            c.lruList.Remove(back)
            delete(c.cache, back.Value.(*entry).key)
        }
    }
    
    elem := c.lruList.PushFront(&entry{key, value})
    c.cache[key] = elem
}

性能对比

  • 数组实现(每次查找 O(n)):1000 ops/s
  • 哈希表+链表(O(1)):100000 ops/s
  • 性能提升:100x

3. 并发优化

案例:并行处理

import (
    "sync"
    "runtime"
)

// ❌ 串行处理
func processDataSerial(data []int) []int {
    result := make([]int, len(data))
    for i, v := range data {
        result[i] = expensiveCompute(v) // 耗时操作
    }
    return result
}

// ✅ 并行处理
func processDataParallel(data []int) []int {
    result := make([]int, len(data))
    numWorkers := runtime.NumCPU()
    chunkSize := (len(data) + numWorkers - 1) / numWorkers
    
    var wg sync.WaitGroup
    
    for i := 0; i < numWorkers; i++ {
        wg.Add(1)
        go func(workerID int) {
            defer wg.Done()
            
            start := workerID * chunkSize
            end := start + chunkSize
            if end > len(data) {
                end = len(data)
            }
            
            for j := start; j < end; j++ {
                result[j] = expensiveCompute(data[j])
            }
        }(i)
    }
    
    wg.Wait()
    return result
}

性能对比(8核CPU):

  • 串行:800ms
  • 并行:120ms
  • 性能提升:6.7x

4. CPU 缓存优化

案例:数据局部性

// ❌ 缓存不友好:按列遍历
func sumColumnsBad(matrix [][]int) int {
    sum := 0
    rows := len(matrix)
    cols := len(matrix[0])
    
    for col := 0; col < cols; col++ {
        for row := 0; row < rows; row++ {
            sum += matrix[row][col] // 缓存未命中
        }
    }
    return sum
}

// ✅ 缓存友好:按行遍历
func sumColumnsGood(matrix [][]int) int {
    sum := 0
    for _, row := range matrix {
        for _, val := range row { // 顺序访问,缓存命中
            sum += val
        }
    }
    return sum
}

性能对比(1000x1000 矩阵):

  • 按列遍历:45ms(缓存未命中率高)
  • 按行遍历:15ms(缓存命中率高)
  • 性能提升:3x

5. 减少函数调用开销

案例:内联优化

// ❌ 频繁函数调用
func calculateSlow(data []int) int {
    sum := 0
    for _, v := range data {
        sum += square(v) // 函数调用开销
    }
    return sum
}

func square(x int) int {
    return x * x
}

// ✅ 内联计算
func calculateFast(data []int) int {
    sum := 0
    for _, v := range data {
        sum += v * v // 直接计算,无调用开销
    }
    return sum
}

Go 编译器优化提示

// 使用 //go:inline 提示编译器内联
//go:inline
func square(x int) int {
    return x * x
}

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

6. 避免不必要的计算

案例:缓存计算结果

import "sync"

// ✅ 使用 sync.Once 避免重复计算
type Config struct {
    once   sync.Once
    result *ExpensiveResult
}

func (c *Config) GetResult() *ExpensiveResult {
    c.once.Do(func() {
        c.result = expensiveComputation() // 只计算一次
    })
    return c.result
}

// ✅ 使用记忆化(Memoization)
type Fibonacci struct {
    cache map[int]int
    mu    sync.RWMutex
}

func (f *Fibonacci) Compute(n int) int {
    // 先读缓存
    f.mu.RLock()
    if val, ok := f.cache[n]; ok {
        f.mu.RUnlock()
        return val
    }
    f.mu.RUnlock()
    
    // 计算并缓存
    f.mu.Lock()
    defer f.mu.Unlock()
    
    // Double-check
    if val, ok := f.cache[n]; ok {
        return val
    }
    
    if n <= 1 {
        f.cache[n] = n
        return n
    }
    
    result := f.Compute(n-1) + f.Compute(n-2)
    f.cache[n] = result
    return result
}

CPU 亲和性绑定

为什么需要 CPU 亲和性?

  • 减少 CPU 缓存失效
  • 减少上下文切换
  • 提升多核性能

Linux 命令绑定

# 绑定进程到 CPU 0-3
taskset -c 0-3 ./my_app

# 绑定运行中的进程
taskset -cp 0-3 <pid>

# 查看进程的 CPU 亲和性
taskset -cp <pid>

Go 代码绑定

import (
    "runtime"
    "syscall"
)

// 设置 Goroutine 到特定 CPU
func setGoroutineAffinity(cpuID int) {
    runtime.LockOSThread()
    defer runtime.UnlockOSThread()
    
    var cpuSet syscall.CPUSet
    cpuSet.Set(cpuID)
    
    err := syscall.SchedSetaffinity(0, &cpuSet)
    if err != nil {
        panic(err)
    }
}

// 使用示例
func worker(cpuID int) {
    setGoroutineAffinity(cpuID)
    
    // 密集计算任务
    for {
        // ...
    }
}

func main() {
    numCPU := runtime.NumCPU()
    
    for i := 0; i < numCPU; i++ {
        go worker(i)
    }
    
    select {}
}

指令级优化

1. SIMD 指令

使用 Go 汇编实现 SIMD

// sum_amd64.s
TEXT ·SumSIMD(SB), NOSPLIT, $0-24
    MOVQ arr+0(FP), SI
    MOVQ len+8(FP), CX
    XORPS X0, X0  // 清零 XMM0

loop:
    MOVUPS (SI), X1   // 加载 4 个 float32
    ADDPS X1, X0      // SIMD 加法
    ADDQ $16, SI
    SUBQ $4, CX
    JA loop

    // 水平求和 XMM0
    MOVHLPS X0, X1
    ADDPS X1, X0
    MOVAPS X0, X1
    SHUFPS $1, X1, X1
    ADDSS X1, X0
    MOVSS X0, ret+16(FP)
    RET

性能对比

  • 普通循环:100ms
  • SIMD 优化:25ms
  • 性能提升:4x

2. 分支预测优化

// ❌ 分支预测失败
func processBad(data []int) int {
    sum := 0
    for _, v := range data {
        if v % 2 == 0 {  // 随机分支,预测失败率高
            sum += v
        }
    }
    return sum
}

// ✅ 减少分支
func processGood(data []int) int {
    sum := 0
    for _, v := range data {
        sum += v * (v % 2)  // 无分支,用算术代替
    }
    return sum
}

CPU 性能分析工具

1. perf(Linux)

# 记录 CPU profile
perf record -g ./my_app

# 查看报告
perf report

# 实时查看热点
perf top

# 查看缓存未命中
perf stat -e cache-misses,cache-references ./my_app

2. pprof(Go)

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

func main() {
    // 启用 pprof
    go http.ListenAndServe("localhost:6060", nil)
    
    // 业务代码
    // ...
}

使用方式

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

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

# 查看 top 函数
go tool pprof -top cpu.prof

3. Flame Graph(火焰图)

# 使用 perf 生成火焰图
perf record -F 99 -g ./my_app
perf script > out.perf
stackcollapse-perf.pl out.perf > out.folded
flamegraph.pl out.folded > flame.svg

火焰图解读

  • X 轴:按字母排序(非时间)
  • Y 轴:调用栈深度
  • 宽度:占用 CPU 时间
  • 颜色:区分不同函数

CPU 优化实战案例

案例:JSON 序列化优化

场景:某 API 需要返回大量 JSON 数据,CPU 占用高

优化步骤

1. 性能分析

go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30

# 发现 encoding/json.Marshal 占用 80% CPU

2. 优化方案

import (
    "encoding/json"
    jsoniter "github.com/json-iterator/go"
)

// ❌ 标准库 JSON
func serializeStd(data interface{}) []byte {
    bytes, _ := json.Marshal(data)
    return bytes
}

// ✅ 高性能 JSON 库
var json = jsoniter.ConfigCompatibleWithStandardLibrary

func serializeFast(data interface{}) []byte {
    bytes, _ := json.Marshal(data)
    return bytes
}

3. 效果验证

# Benchmark 对比
BenchmarkStdJSON-8     1000   1200000 ns/op
BenchmarkFastJSON-8    5000    250000 ns/op

性能提升:4.8x

案例:字符串拼接优化

import (
    "strings"
    "bytes"
)

// ❌ 低效:每次拼接都创建新字符串
func concatSlow(strs []string) string {
    result := ""
    for _, s := range strs {
        result += s  // O(n²) 复杂度
    }
    return result
}

// ✅ 高效:使用 strings.Builder
func concatFast(strs []string) string {
    var builder strings.Builder
    builder.Grow(estimateSize(strs)) // 预分配容量
    
    for _, s := range strs {
        builder.WriteString(s)  // O(n) 复杂度
    }
    return builder.String()
}

func estimateSize(strs []string) int {
    size := 0
    for _, s := range strs {
        size += len(s)
    }
    return size
}

Benchmark 对比

BenchmarkConcatSlow-8    1000   1500000 ns/op   500000 B/op   1000 allocs/op
BenchmarkConcatFast-8   50000     25000 ns/op     8192 B/op      1 allocs/op

性能提升:60x


CPU 优化检查清单

算法层

  • 时间复杂度是否最优?(O(n²) → O(n log n) → O(n))
  • 是否使用了合适的数据结构?(哈希表、堆、树)
  • 是否避免了重复计算?(缓存、记忆化)

并发层

  • 是否充分利用多核?(并行处理)
  • 是否减少了锁竞争?(细粒度锁、无锁数据结构)
  • 是否使用了 Goroutine Pool?(避免频繁创建)

代码层

  • 是否减少了函数调用?(内联)
  • 是否优化了循环?(循环展开、向量化)
  • 是否利用了 CPU 缓存?(数据局部性)
  • 是否减少了分支?(分支预测)

系统层

  • 是否绑定了 CPU 亲和性?
  • 是否调优了 GOMAXPROCS?
  • 是否使用了编译器优化?(-O2、-O3)

本章小结

核心要点

  1. 算法优先:时间复杂度的优化收益最大
  2. 并发利用:充分利用多核 CPU
  3. 缓存友好:优化数据访问模式,提升缓存命中率
  4. 减少开销:减少函数调用、内存分配
  5. 工具分析:使用 perf、pprof、火焰图定位瓶颈

优化优先级

算法优化 > 并发优化 > 缓存优化 > 代码细节优化

⏮️ 上一节:方法论 | ⏭️ 下一节:IO优化