AMD64/Linux 低延迟调优指南
本指南介绍如何为实时或低延迟工作负载调优 AMD64/x86-64 硬件与 Linux。目标应用包括线速抓包、深度包检测、内核旁路网络以及对 CPU 密集型代码的精确基准测试。
本文中的”延迟”指事件发生到处理完成之间的时间 —— 例如从网卡收到数据包到应用完成对该包处理为止。
两个抓手是:
- 最大化单核性能 —— 提升 CPU 频率、关闭节能特性。
- 最小化抖动(jitter) —— 减少定时器、中断以及竞争线程带来的打扰。
你可以用 hiccups 工具来测量抖动减小。隔离出 core 3 之后,输出大致是:
$ hiccups | column -t -R 1,2,3,4,5,6
cpu threshold_ns hiccups pct99_ns pct999_ns max_ns
0 168 17110 83697 6590444 17010845
1 168 9929 169555 5787333 9517076
2 168 20728 73359 6008866 16008460
3 168 28336 1354 4870 17869
core 3 的最大抖动降到 18 微秒,其他核仍在数十毫秒量级。
硬件
电源配置
把 UEFI/BIOS 电源配置设为 Maximum Performance。它会关闭激进的 C-state,让 CPU 对负载立即响应。
关闭 SMT / 超线程
SMT 通过在同一物理核上的逻辑核之间共享执行单元,来提升”受限于 IPC”的工作负载的吞吐。但对延迟敏感的代码,共享反而引入资源争用和抖动。
关闭 SMT 也会把每物理核的有效 L1/L2 缓存翻倍(在 2-way SMT 系统上)。
推荐顺序:
- 在 UEFI/BIOS 中关闭。
- 启动内核参数:
nosmt - 运行时:
echo off > /sys/devices/system/cpu/smt/control - 在查询拓扑后热卸载兄弟线程:
lscpu --extended
cat /sys/devices/system/cpu/cpu*/topology/thread_siblings_list
验证:
cat /sys/devices/system/cpu/smt/active # 期望为 0
Turbo Boost
Intel Turbo Boost 和 AMD Turbo Core 在热和功率余量允许时,让 CPU 持续高于基础频率运行。在良好散热、部分核被关闭的情况下,剩余核能持续跑在最高 boost 频率。
cat /sys/devices/system/cpu/intel_pstate/no_turbo # 0 = 启用 boost
cpupower frequency-info
生产环境不要运行 turbostat —— 它会引入调度抖动。
内核
CPU 频率调节器
把所有核强制到最高频率:
find /sys/devices/system/cpu -name scaling_governor \
-exec sh -c 'echo performance > {}' ';'
或通过 tuned:
tuned-adm profile latency-performance
核心隔离
内核调度器默认在所有核之间做负载均衡。通过 isolcpus 内核命令行把指定核排除在通用调度池之外:
isolcpus=1-7
你的应用线程必须显式通过 taskset 或 sched_setaffinity 绑定到这些核。
内核线程仍会在被隔离的核上产生。把它们迁移到 core 0:
pgrep -P 2 | xargs -i taskset -p -c 0 {}
或使用 tuna:
tuna --cpus=1-7 --isolate
tuna -P # 验证线程亲和性
迁移内核 workqueue:
find /sys/devices/virtual/workqueue -name cpumask \
-exec sh -c 'echo 1 > {}' ';'
验证:
perf stat -e 'sched:sched_switch' -a -A --timeout 10000
被隔离的核应显示几乎为零的上下文切换。
定时器抑制
调度器定时器会在每个核周期性触发以抢占线程。在只跑一个线程的被隔离的核上抑制它:
# 内核命令行
nohz_full=1-7 rcu_nocbs=1-7
只有当核上恰好只有一个可运行线程时,tick 才会被抑制。查看 /proc/sched_debug 验证。
延长 VM stats 更新间隔,减少相关 wakeup:
sysctl vm.stat_interval=120
验证:
perf stat -e 'irq_vectors:local_timer_entry' -a -A --timeout 30000
被隔离的核应每秒只有大约 1–2 次定时器中断。目前无法完全消除。
IRQ 亲和性
把中断处理从被隔离的核上移走。让 irqbalance 自动来做(它会尊重 isolcpus):
irqbalance --foreground --oneshot
或手动 ban 指定核(core 3 = 位掩码 0x8):
IRQBALANCE_BANNED_CPUS=8 irqbalance --foreground --oneshot
查看当前亲和性:
find /proc/irq/ -name smp_affinity_list -print -exec cat '{}' ';'
watch cat /proc/interrupts
网络栈
对低延迟网络,请彻底避开 Linux 内核网络栈。内核旁路选项:
- DPDK
- OpenOnload(Solarflare)
- Mellanox VMA
- Exablaze
如果必须走内核栈,请参考 Red Hat 的性能调优指南和 Cloudflare 关于低延迟 10 Gbps 网络的文章。
内存
禁用交换分区
访问已被换到磁盘的匿名内存会触发 major page fault,造成一次磁盘读时长的停顿。彻底关闭 swap:
swapoff -a
然后把当前和未来的分配都锁定:
mlockall(MCL_CURRENT | MCL_FUTURE);
关闭透明大页
THP 会自动把 4 KiB 页提升为 2 MiB 页,降低 TLB 压力。但 khugepaged 和 kcompactd 的提升过程和后台合并会因为修改页表、触发 TLB shootdown 而引入延迟尖峰。
启动时关闭:
transparent_hugepage=never
或运行时关闭:
echo never > /sys/kernel/mm/transparent_hugepage/enabled
改用显式大页(MAP_HUGETLB 或对 THP 敏感的分配器)。
关闭自动 NUMA 平衡
Linux 的自动 NUMA 平衡会把页迁移到”访问它最多的那个 NUMA 节点”,使用 page fault 作为信号。这会触发 TLB flush,也带来不可预测的 page fault 延迟。
echo 0 > /proc/sys/kernel/numa_balancing
同时确认 numad 没有运行。
关闭 KSM
Kernel Samepage Merging 会去重相同的页,但合并过程需要锁页表并 flush TLB。
echo 0 > /sys/kernel/mm/ksm/run
Spectre/Meltdown 缓解
针对 CPU 漏洞(Spectre、Meltdown、MDS)的软件缓解措施会带来性能开销,具体取决于工作负载。在被隔离、可信任的系统上,可以关闭它们:
mitigations=off # 内核命令行
缓存分区
如果 CPU 支持 Intel Cache Allocation Technology(CAT),可以对 LLC 分区,把大多数 cache way 分配给延迟敏感应用。用 intel-cmt-cat 配置。
应用
防止 page fault
在启动早期调用 mlockall(MCL_CURRENT | MCL_FUTURE)。它会预触发所有已映射页面的 fault,并阻止后续分配再 fault。
使用大页
默认 4 KiB 页意味着 2048 项的 TLB 只能覆盖 8 MiB 工作集。对工作集更大的应用,2 MiB 或 1 GiB 大页能显著减少 TLB miss。
监控 TLB 行为:
perf stat -e 'dTLB-loads,dTLB-load-misses,iTLB-loads,iTLB-load-misses' \
-a --timeout 10000
避免 TLB shootdown
TLB shootdown 由页表修改触发 —— 内核向每个核发送 IPI 以使过期的 TLB 项无效。来源包括:
munmap、mprotect- glibc malloc 在已释放内存上调用
madvise(MADV_FREE)或munmap - THP 提升和合并
- KSM 合并
- NUMA 页迁移
- page cache writeback
避免办法:启动时就映射好所有内存、之后不释放;关闭 THP、KSM 和自动 NUMA 平衡;避免可写的文件映射。
监控:
egrep 'TLB|CPU' /proc/interrupts
perf stat -e 'tlb:tlb_flush' -a -A --timeout 10000
调度策略
在被隔离的核上运行一个使用忙轮询的单一线程时,优先选择 SCHED_OTHER,而不是 SCHED_FIFO 或 SCHED_RR。实时优先级的线程如果永不让出 CPU,会饿死内核任务(比如 vmstat),可能锁死系统。
Linux 默认把实时任务限制在 CPU 时间的 95%(/proc/sys/kernel/sched_rt_runtime_us)。如果需要实时调度,请相应调整这个上限。