强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Nim 完全指南 / 21 性能优化

第 21 章:性能优化

21.1 编译优化标志

标志说明适用场景
-d:release开启优化,关闭检查生产环境
-d:danger最大优化,关闭所有安全检查性能基准
--opt:size优化大小嵌入式/移动端
--opt:speed优化速度服务器
--mm:orcORC 内存管理默认推荐
# Release 模式(推荐生产使用)
nim c -d:release -o:app src/app.nim

# Danger 模式(仅基准测试)
nim c -d:danger -o:app src/app.nim

# 自定义优化
nim c -d:release --passC:"-O3 -march=native" -o:app src/app.nim

21.2 内存管理

21.2.1 ORC(默认)

ORC 是 Nim 2.0 的默认内存管理器,结合引用计数和循环收集器:

# ORC 自动管理内存
type Node = ref object
  value: int
  next: Node

# 即使有循环引用,ORC 也能正确回收
var a = Node(value: 1)
var b = Node(value: 2)
a.next = b
b.next = a  # 循环引用
# ORC 的循环收集器会处理这种情况

21.2.2 内存优化技巧

# 1. 使用值类型代替引用类型
type Point = object  # 值类型,栈分配
  x, y: float

# 2. 预分配序列容量
var data = newSeqOfCap[int](10000)  # 预分配

# 3. 避免不必要的字符串拷贝
proc process(s: lent string) =  # 借用,零拷贝
  echo s.len

# 4. 使用 set 代替 seq 进行成员检查
let validIds = {1, 2, 3, 4, 5}  # 位图,O(1) 查找
if id in validIds:
  discard

# 5. 及时释放大对象
var bigData = newSeq[int](1000000)
# 使用完毕后
bigData = @[]  # 释放内存

21.2.3 深拷贝控制

type LargeBuffer = object
  data: seq[int]

# 默认:引用计数共享
var a = LargeBuffer(data: newSeq[int](1000000))
var b = a  # 共享数据,不拷贝

# 显式深拷贝
var c = deepCopy(a)  # 独立副本

# 移动语义
proc consume(x: sink LargeBuffer) =
  # x 的所有权转移到此过程
  echo x.data.len

var d = LargeBuffer(data: newSeq[int](100))
consume(d)  # d 不再有效

21.3 Profiling

21.3.1 CPU Profiling

import std/[times, strformat]

# 手动计时
template benchmark(name: string, body: untyped) =
  let start = cpuTime()
  body
  let elapsed = cpuTime() - start
  echo &"{name}: {elapsed * 1000:.2f} ms"

benchmark "排序 100 万个元素":
  var data = newSeq[int](1_000_000)
  for i in 0..<data.len:
    data[i] = rand(1_000_000)
  data.sort()

21.3.2 使用 perf 工具

# Linux perf
nim c -d:release --debugger:native -o:app src/app.nim
perf record ./app
perf report

# 查看热点函数
perf report --sort=dso,symbol

21.3.3 内存分析

import std/[memfiles, strutils, strformat]

# 监控内存使用
proc getMemoryUsage(): int =
  when defined(linux):
    let status = readFile("/proc/self/status")
    for line in status.splitLines():
      if line.startsWith("VmRSS:"):
        return parseInt(line.splitWhitespace()[1]) * 1024  # KB to bytes
  0

echo &"Memory: {getMemoryUsage() div 1024} KB"

# 使用 valgrind 检测内存泄漏
# valgrind --leak-check=full ./app

21.4 数据结构选择

场景推荐原因
频繁随机访问array / seqO(1) 访问
频繁插入删除DoublyLinkedListO(1) 插入删除
键值查找TableO(1) 查找
成员检查HashSet / setO(1) 查找
有序数据seq + sort缓存友好
队列DequeO(1) 两端操作
import std/[tables, deques, sets]

# 频繁查找用 Table
var cache = initTable[string, int]()
for i in 0..10000:
  cache[$i] = i
echo cache["5000"]  # O(1)

# 成员检查用 HashSet
var visited = initHashSet[int]()
for i in 0..100000:
  visited.incl(i)
echo 50000 in visited  # O(1)

# 队列用 Deque
var queue = initDeque[int]()
for i in 0..1000:
  queue.addLast(i)
echo queue.popFirst()  # O(1)

21.5 算法优化

21.5.1 避免不必要的分配

# 差:每次迭代分配新序列
proc bad(data: seq[int]): seq[int] =
  result = @[]
  for x in data:
    if x > 0:
      result.add(x)

# 好:预分配
proc good(data: seq[int]): seq[int] =
  result = newSeqOfCap[int](data.len)
  for x in data:
    if x > 0:
      result.add(x)

# 最好:原地过滤
proc best(data: var seq[int]) =
  var writeIdx = 0
  for x in data:
    if x > 0:
      data[writeIdx] = x
      inc writeIdx
  data.setLen(writeIdx)

21.5.2 SIMD 优化

# 使用 SIMD 指令加速向量运算
{.passC: "-mavx2".}
{.pragma: simd, header: "<immintrin.h>".}

type M256d {.importc: "__m256d", simd.} = object

proc mm256_loadu_pd(mem: ptr float64): M256d {.importc: "_mm256_loadu_pd", simd.}
proc mm256_add_pd(a, b: M256d): M256d {.importc: "_mm256_add_pd", simd.}
proc mm256_storeu_pd(mem: ptr float64, a: M256d) {.importc: "_mm256_storeu_pd", simd.}

proc vectorAdd(a, b, result: ptr float64, len: int) =
  var i = 0
  while i + 4 <= len:
    let va = mm256_loadu_pd(addr a[i])
    let vb = mm256_loadu_pd(addr b[i])
    let vr = mm256_add_pd(va, vb)
    mm256_storeu_pd(addr result[i], vr)
    i += 4
  while i < len:
    result[i] = a[i] + b[i]
    inc i

21.5.3 并行化

import std/threadpool

proc parallelMap[T, U](data: seq[T], f: proc(x: T): U): seq[U] =
  result = newSeq[U](data.len)
  parallel:
    for i in 0..<data.len:
      result[i] = spawn f(data[i])

let data = toSeq(1..1000000)
let squares = parallelMap(data, proc(x: int): int = x * x)

21.6 实战示例

🏢 性能对比测试

import std/[times, algorithm, sequtils, random, strformat]

proc benchmarkSort(n: int) =
  var data = newSeq[int](n)
  for i in 0..<n:
    data[i] = rand(n)
  
  # QuickSort (default)
  var d1 = data
  let t1 = cpuTime()
  d1.sort()
  let e1 = cpuTime() - t1
  
  # 自定义排序
  var d2 = data
  let t2 = cpuTime()
  d2.sort(SortOrder.Ascending)
  let e2 = cpuTime() - t2
  
  echo &"n={n}: sort={e1*1000:.2f}ms, sort(order)={e2*1000:.2f}ms"

for n in [1000, 10000, 100000, 1000000]:
  benchmarkSort(n)

本章小结

优化手段效果难度
-d:release2-10x 加速
选择正确数据结构10-100x 加速
减少内存分配2-5x 加速
SIMD 优化4-8x 加速
并行化N 倍(N 为核数)

练习

  1. 对比 Debug 和 Release 模式的性能差异
  2. 使用 perf 分析一个程序的热点
  3. 优化一个使用了不恰当数据结构的程序

扩展阅读


上一章:容器化部署 | 下一章:C 后端深入