Nim 完全指南 / 21 性能优化
第 21 章:性能优化
21.1 编译优化标志
| 标志 | 说明 | 适用场景 |
|---|
-d:release | 开启优化,关闭检查 | 生产环境 |
-d:danger | 最大优化,关闭所有安全检查 | 性能基准 |
--opt:size | 优化大小 | 嵌入式/移动端 |
--opt:speed | 优化速度 | 服务器 |
--mm:orc | ORC 内存管理 | 默认推荐 |
# 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 / seq | O(1) 访问 |
| 频繁插入删除 | DoublyLinkedList | O(1) 插入删除 |
| 键值查找 | Table | O(1) 查找 |
| 成员检查 | HashSet / set | O(1) 查找 |
| 有序数据 | seq + sort | 缓存友好 |
| 队列 | Deque | O(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:release | 2-10x 加速 | 低 |
| 选择正确数据结构 | 10-100x 加速 | 中 |
| 减少内存分配 | 2-5x 加速 | 中 |
| SIMD 优化 | 4-8x 加速 | 高 |
| 并行化 | N 倍(N 为核数) | 高 |
练习
- 对比 Debug 和 Release 模式的性能差异
- 使用 perf 分析一个程序的热点
- 优化一个使用了不恰当数据结构的程序
扩展阅读
← 上一章:容器化部署 | 下一章:C 后端深入 →