yuqi-zheng

AMD64/Linux 低延迟调优指南


本指南介绍如何为实时或低延迟工作负载调优 AMD64/x86-64 硬件与 Linux。目标应用包括线速抓包、深度包检测、内核旁路网络以及对 CPU 密集型代码的精确基准测试。

本文中的”延迟”指事件发生到处理完成之间的时间 —— 例如从网卡收到数据包到应用完成对该包处理为止。

两个抓手是:

  1. 最大化单核性能 —— 提升 CPU 频率、关闭节能特性。
  2. 最小化抖动(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 系统上)。

推荐顺序:

  1. 在 UEFI/BIOS 中关闭。
  2. 启动内核参数:nosmt
  3. 运行时:echo off > /sys/devices/system/cpu/smt/control
  4. 在查询拓扑后热卸载兄弟线程:
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

你的应用线程必须显式通过 tasksetsched_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 压力。但 khugepagedkcompactd 的提升过程和后台合并会因为修改页表、触发 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 项无效。来源包括:

  • munmapmprotect
  • 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_FIFOSCHED_RR。实时优先级的线程如果永不让出 CPU,会饿死内核任务(比如 vmstat),可能锁死系统。

Linux 默认把实时任务限制在 CPU 时间的 95%(/proc/sys/kernel/sched_rt_runtime_us)。如果需要实时调度,请相应调整这个上限。