CPU 架构与性能影响因素:缓存、超线程、NUMA
我在做性能调优的头两年,有个很大的困惑:明明代码逻辑一样,换台机器就跑慢了。后来才明白,CPU 架构的差异才是真正的“幕后黑手”。这一章咱们就聊聊 CPU 里的几个关键设计——缓存、超线程、NUMA——它们怎么影响性能,以及我们在监控时该怎么看待这些指标。
1. 缓存层级:离 CPU 越近,速度越贵
现代 CPU 都用了多级缓存。说白了,就是给 CPU 配了几个不同速度的“小仓库”。
- L1 缓存:分指令缓存和数据缓存,一般 32KB~64KB,延迟 3~4 个周期。
- L2 缓存:每个核心私有,256KB~1MB,延迟 10~20 个周期。
- L3 缓存:多核心共享,几 MB 到几十 MB,延迟 30~60 个周期。
- 内存:主存,几十 GB,延迟 100~300 个周期。
缓存命中率对性能的影响非常大。用 perf stat -e cache-misses 就能看到真实的 miss 数量。我一般这样快速判断:
# 查看 L1 数据缓存 miss 率
perf stat -e L1-dcache-load-misses,L1-dcache-loads -a -- sleep 5
# 查看 LLC(最后一级缓存) miss 率
perf stat -e LLC-load-misses,LLC-loads -a -- sleep 5
| 层级 | 典型大小 | 延迟(周期) | 共享范围 |
|---|---|---|---|
| L1 | 32~64 KB | 3~4 | 单核心 |
| L2 | 256 KB~1 MB | 10~20 | 单核心 |
| L3 | 8~32 MB | 30~60 | 所有核心(或某簇) |
| 主存 | 16~512 GB | 100~300 | 全系统 |
cache-misses 和 LLC-load-misses,如果 miss 率超过 5%,大概率是数据局部性不好,需要优化。
2. 超线程:虚拟核心,资源竞争
超线程(Hyper-Threading,HT)把一个物理核心模拟成两个逻辑核心。好处是能多塞一道指令流,提高执行单元利用率。但坏处也很明显——两个线程共享 L1/L2 缓存和执行资源,一旦互相打架,性能反而下降。
我记得有一次做数据库压测,开启 HT 后,TPS 只涨了 10%,但 CPU 占用率却高了 30%。查了 htop 发现,某些逻辑核心在抢同一份 L1 缓存。最后我关掉 HT,单核跑满反而更稳定。
在 Linux 里查看 HT 状态:
# 查看物理核心数与逻辑核心数
lscpu | grep -E 'Thread|Core|Socket'
# 如果 Thread(s) per core 大于 1,说明 HT 已开启
# 临时关闭所有 HT(谨慎操作,生产环境别乱试)
echo off > /sys/devices/system/cpu/smt/control
3. NUMA:非一致性内存访问
NUMA(Non-Uniform Memory Access)在多路服务器上很常见。每个 CPU 有自己的本地内存,访问本地的快,访问远端(另一个 CPU 的内存)要过 QPI/UPI 总线,延迟高不少。我见过太多人部署 Redis 时,数据全放在 node 0 上,但进程被调度到 node 1,结果内存访问延迟暴增。
- 本地内存访问:延迟正常,带宽高。
- 远端内存访问:延迟高 20%~50%,带宽低。
- 跨 NUMA 节点通信:还会占用总线,影响整体吞吐。
查看 NUMA 拓扑:
# 显示 NUMA 节点信息
numactl --hardware
# 查看当前进程的内存分配在哪
numastat -p <pid>
numactl –cpunodebind=0 –membind=0 绑定进程到固定 CPU 和内存节点。对于 PostgreSQL 这类服务,每个实例绑定一个 NUMA 节点,能避免跨节点抢内存。
NUMA 对性能的影响可以用 perf stat -e remote_access 来监控。如果 remote/local 比例超过 0.1,说明内存分配策略有问题。
4. 缓存一致性协议:MESI 和它的开销
多核心共享缓存时,必须保证数据一致性。MESI 协议(Modified, Exclusive, Shared, Invalid)就是干这个的。但它有个代价——当两个核心频繁写同一个 cache line 时,会触发“缓存乒乓”,性能断崖式下降。
我遇到过的一个典型场景:两个线程各操作一个数组,但数组恰好挨着(同一 cache line),结果每次写都要通知另一个核心 invalidation,吞吐直接降到单核水平。解决办法很简单:在变量间加 padding,让它们落在不同 cache line。
perf c2c 工具检测缓存行冲突。输出会告诉你哪些 cache line 上发生了“真实分享”(true sharing)和“伪分享”(false sharing)。
5. 综合知识结构
下面这张图概括了 CPU 架构中影响性能的关键因素及它们之间的关系。
6. 实战中的监控思路
我在做性能瓶颈排查时,一般按这个顺序:
- 先用
top/htop看 CPU 总体使用率,确认不是 CPU 过载。 - 然后用
perf stat看 cache-misses 和 instruction per cycle(IPC)。IPC 太低说明 CPU 经常停等缓存。 - 再用
numactl –hardware检查 NUMA 拓扑,结合numastat看内存分布。 - 如果怀疑超线程冲突,用
perf c2c record采集,最后看输出报告。
好了,这章的内容就这些。缓存、超线程、NUMA 这三样东西,你弄明白了,调优就有方向了。记住一个原则:让数据离 CPU 最近,让线程待在本地。后面我们会具体讲怎么用工具抓到这些指标。