跳转到内容

主机与操作系统延迟参数

参数参考讲的是 Aeron 的 传输 旋钮。本页讲的是其下的一层:决定那些传输旋钮 能否运行在一个安静、可预测的核上的 主机、操作系统与 JVM 设置。几乎每一个在缓冲区正确取值后仍然存在的 毫秒级尾延迟,最终都能追溯到这一层的某个原因 —— 某个核掉进了深度 C-state、时钟在负载下回落、khugepaged 正在压缩大页,或一个 IRQ 落到了远端 NUMA 节点上。

对每一个系统旋钮,除了它的延迟影响之外,还有两件事至关重要:它住在哪里,以及 它能持续多久。人们最常 搞错的就是持久化模型。

持久化类别通过什么设置重启后保留?说明
GRUB / 内核引导参数grubby --update-kernel=ALL --args=... 然后重启✅ 是唯一 需要 重启才能生效的类别。在基于 BLS 的发行版(Amazon Linux 2023、较新的 Fedora/RHEL)上 grubby 是权威 —— 仅编辑 /etc/default/grub 不够。
sysfs / proc(即时)echo ... > /sys/...❌ 否 —— 重启即丢失立刻作用于运行中的内核。无需重启,无需重启进程。重启后丢失,除非重新应用。
systemd unit / tuned 配置一个在开机时写入该 sysfs 值的 unit/profile✅ 是让即时 sysfs 变更 持久化 而又无需重启的方式 —— 现在即时应用,让 unit 在每次开机时重新应用。
进程参数启动脚本 / 服务参数✅ 是(每次启动重新读取)例如 JVM 参数。持久是因为启动器每次都重新 exec 它们 —— 但需要 重启进程(而非重启机器)才能生效。

在一台看似空闲的机器上,µs 到 ms 级抖动的最大单一来源就是 CPU 切换功耗状态。已经掉进深度 idle 状态的核, 唤醒要花数微秒;按需爬坡的时钟,给每一个安静间隙之后的首条消息都加上方差。

参数吞吐p50p99(尾部)通过什么设置持久回退
C-stateintel_idle.max_cstate=1processor.max_cstate=1可忽略小幅收益 —— 核不在突发之间入睡大幅收益 —— 消除空闲间隙后首包的深睡唤醒延迟GRUB 引导参数✅ 重启grubby --remove-args=... + 重启
CPU 调速器performance负载下保持高时钟降低基线消除调速器爬坡滞后cpupower frequency-set -g performance(sysfs)❌ 即时改回 schedutil/ondemand
P-state 驱动模式intel_pstate=passive / amd_pstate=guided不适用不适用让调速器(而非不透明的 HWP)掌控时钟,使锁频真正生效GRUB 引导参数✅ 重启移除参数 + 重启
频率锁定scaling_min_freq = scaling_max_freq略低于突发峰值死平的基线彻底消除时钟方差 —— 每个核同一频率sysfs 逐 CPU,或 cpupower frequency-set -d F -u F❌ 即时写回 cpuinfo_min/cpuinfo_max
Turbo / Boostintel_pstate/no_turbocpufreq/boost更高的单核峰值不适用Turbo 抬高 峰值但 增加 方差;关闭则锁在基频(更低,但死平均匀)sysfs❌ 即时重新开启(no_turbo=0 / boost=1

数据手册上的「最大 Turbo」(如 3.8–4.5 GHz)是一个 单核 数字 —— 只有在大多数核空闲、整个封装有热/功耗 余量时才可达。一旦你把 所有 核都打满,硬件就强制大家回落到更低的 全核 Turbo 档。所以「最大 GHz」与 「负载下的全核同频」是互斥的:你无法让每个核同时跑在单核峰值。

做延迟工作,你几乎总是要 同频优先于最大值。一个在所有核上都平的时钟 —— 哪怕值更低 —— 能消除一整类跨核 方差。配方如下:

  1. 解除任何锁频,让机器在满载下找到它 天然的全核档(测量它 —— 见下一节)。
  2. scaling_min_freq = scaling_max_freq 锁在那个测得的档 上或略低于它,使任何核都无需回落。

这是人们会跳过的验证步骤 —— 然后一台「已锁频」的机器悄悄地在各核上跑着三个不同的时钟。方法是:把每个核打到 100% 繁忙,然后读取实际的逐核频率并检查离散度。

Terminal window
# 策略被设成了什么(每个唯一的 min/max 对一行 —— 预期恰好一行):
paste \
<(cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_min_freq) \
<(cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_max_freq) | sort -u
# 驱动 + 调速器 + Turbo 状态:
cat /sys/devices/system/cpu/cpufreq/policy0/scaling_driver # intel_cpufreq | acpi-cpufreq | amd-pstate
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor # 预期:performance
cat /sys/devices/system/cpu/intel_pstate/no_turbo 2>/dev/null # 1 = Turbo 关闭

所设的锁频是必要的,但 并不充分 —— 该 SKU 在全核负载下可能并不遵守它。

第 2 步 —— 压满每个核并读取 实际达到的 频率

Section titled “第 2 步 —— 压满每个核并读取 实际达到的 频率”

turbostat 报告 Bzy_MHz —— 每个核 繁忙时的实际频率,这才是关键的数字。最干净的做法是用 turbostat 自带的 stress 子命令,它在一条自包含的命令里同时打满每个逻辑 CPU 采样(无需安装 stress-ng):

Terminal window
# 首选 —— turbostat 自带的全核负载。给每个逻辑 CPU 绑一个繁忙线程,持续 SEC 秒:
sudo turbostat stress --cpu "$(nproc)" -t 30
# 仅观测(当前工作负载,不加额外负载),带上关键列:
sudo turbostat --interval 2 --show Core,CPU,Busy%,Bzy_MHz,Avg_MHz,TSC_MHz,C1%,IPC,IRQ
# 若 turbostat 没有 stress 子命令则用此回退 —— 每个 CPU 一个忙循环:
for i in $(seq 1 "$(nproc)"); do yes > /dev/null & done
turbostat --interval 1 --num_iterations 10 --quiet
pkill yes # 之后务必停掉负载

从输出里读两样东西:

  • 汇总行(CPU 列为 -)给出 全核平均 Bzy_MHz
  • 每个逐核行给出该核的 Bzy_MHz。在各核之间计算 min / avg / max
若 (max − min) / avg ≤ 约 1% 则视为平
观测到的现象判定
所有核彼此、以及与锁频值都在约 1% 以内确实全核同频 —— 锁频被遵守
负载下少数核低于其余核⚠️ 锁频高于全核档 → 把锁频降到回落的下限
每个核都跑在所设 scaling_max_freq 之下🔴 硬性 SKU 上限 —— 全核档低于你的设定;按实际达到值锁频,而非按所设值
仅前 1–2 个采样离散,随后收敛冷机的爬坡滞后,而非真实差异 —— 判断前先热身 ≥5 秒

这同一个循环也是你 发现 天然全核档的方法:解除锁频运行(scaling_max_freq = cpuinfo_max_freq),读取 持续的全核 Bzy_MHz,然后锁在它之上或略低处。

参数吞吐p50p99(尾部)通过什么设置持久回退
SMT / 超线程给非关键工作更多逻辑 CPU若热线程从不共享同一核则中性两个热线程落在同一物理核上(SMT 兄弟)会争用 → 尾部尖峰;让关键 agent 远离兄弟核/sys/devices/system/cpu/smt/control(sysfs)❌ 即时echo on > .../smt/control
核隔离与绑定提升可持续上限 —— 无 run-queue 争用减少上下文切换成本最强尾部杠杆之一 —— 见 核隔离与线程绑定isolcpus/nohz_full(GRUB)+ taskset/numactl(进程)混合视方法而定
Tickless + RCU 卸载nohz_full=<cpus>rcu_nocbs=<cpus>不适用不适用从所列 CPU 上移除周期性调度 时钟 tickRCU softirq 回调 → 不再周期性打断 busy-spin agent。把 CPU 列表与你绑定的核对应起来GRUB 引导参数✅ 重启grubby --remove-args=... + 重启
NUMA 放置(线程 + 内存靠近网卡)消除跨 socket 流量减少内存/PCIe 穿越成本 —— 避免远端 NUMA 惩罚;接收端停顿的修复就在这里。见 NUMA 与缓存局部性numactl --cpunodebind --membind 包裹进程重启进程去掉包裹
IRQ 亲和性(网卡 IRQ → 网卡本地核)让 RX 远离繁忙核降低中断处理抖动阻止网卡中断抢占接收端 agent/proc/irq/<n>/smp_affinity_list(sysfs)❌ 即时恢复原掩码
irqbalance不适用不适用守护进程在运行时重新洗牌 IRQ → 亲和性周期性漂移 → 偶发尖峰;绑核场景应 关闭systemctl disable --now irqbalancesystemctl enable --now irqbalance
kernel.numa_balancing不适用消除页迁移扫描成本阻止自动 NUMA 页迁移停顿热线程sysctl / 99-*.conf✅(文件)改回 1
参数吞吐p50p99(尾部)通过什么设置持久回退
透明大页(THP)模式大堆上更少 TLB 未命中大页支撑的堆略低alwayskhugepaged 进程级压缩页 → 周期性停顿;madvise 更温和;never 最确定/sys/kernel/mm/transparent_hugepage/{enabled,defrag}(sysfs)❌ 即时(或 GRUB transparent_hugepage=neverecho madvise > ...
JVM +UseTransparentHugePages堆上的 TLB 收益always/madvise 下 JVM 把堆 madvise 进 THP;关闭(-UseTransparentHugePages)则让堆彻底脱离 THP启动脚本中的 JVM 参数重启进程翻回参数,重启
显式大页池nr_hugepages内核旁路(DPDK)的 mbuf 池所必需预先保留,从不压缩 → 无 khugepaged 抖动逐 NUMA 节点的 sysfs❌ 即时把池设回 0
vm.swappiness=0避免回收停顿阻止内核在内存压力下换出热页sysctl✅(文件)默认 60
vm.compaction_proactiveness=0关闭 主动 后台内存压缩 —— 其页迁移扫描会偷走周期并在运行期触碰热页sysctl✅(文件)默认 20
vm.max_map_count(调高)允许许多映射的缓冲/term 文件防止多 stream 时 mmap 失败sysctl✅(文件)默认

这些与传输层的 接收路径缓冲区 有重叠;这里讲的是限制应用可申请上限的 操作系统级别参数。

参数吞吐p50p99(尾部)通过什么设置持久回退
net.core.rmem_max / wmem_max提供吸收突发而不丢包的余量过小 → RX 环/socket 溢出 → NAK 恢复尖峰;按突发扇入的数倍 BDP 取值sysctl / 99-*.conf✅(文件)发行版默认(约 212 KB)
net.core.netdev_max_backlog更深的逐 CPU 入口队列当包到达快于协议栈排空时防止丢包sysctl✅(文件)默认
net.ipv4.ip_local_reserved_ports不适用不适用保留应用的控制/数据端口,使内核绝不把它们临时分配给一个 ephemeral socket 而冲突 —— 是可用性/抖动坑,而非吞吐旋钮sysctl✅(文件)清除该范围
网卡环形缓冲(如 ENA)环匹配带宽设置正确则排队低过深的环增加队列膨胀延迟;过小则在突发时丢包ethtool -G❌ 即时先前的环大小
参数作用持久
-XX:+UseZGC(或一个低停顿收集器)亚毫秒级 GC 停顿 → 消除 GC 对尾部的贡献进程参数
-XX:+AlwaysPreTouch启动时触碰整个堆 → 运行期不再有首次触碰缺页停顿(与大页配合)进程参数
-XX:+UseTransparentHugePages把堆 madvise 进 THP —— 何时该把它 关掉 见上文 THP 说明进程参数
JIT 预热上线前用代表性流量跑遍所有热路径 —— 冷代码 + 首次触碰会造成严重的早期尖峰。见 预热 JVM运行期流程

任何跨主机的延迟测量(单向分段计时、相位对齐的突发)都要求各主机共享一个亚微秒级的时间基准,否则你的数字 就是噪声。

参数为何重要通过什么设置持久
PTP / 带硬件 PHC 的 chrony把每台主机校准到一个共同的 ns 级参考(例如网卡的 PTP 硬件时钟)→ 诚实的单向分段计算与可行的挂钟突发对齐refclock PHCchronyd✅ 服务
需要重启机器?需要重启应用?重启后丢失,除非……
C-state、P-state 驱动模式、isolcpusnohz_fullrcu_nocbs✅(引导参数)—(GRUB 持久)
调速器、频率锁定、Turbo、THP sysfs、IRQ 亲和性、网卡环、SMT由 systemd unit / tuned 配置重新应用
sysctl 值(rmem_maxswappinessnuma_balancingcompaction_proactiveness……)写入 /etc/sysctl.d/*.conf
JVM 参数、numactl 进程包裹、systemd CPUAffinity—(在启动脚本 / unit 中)
显式大页池否(但 DPDK 应用启动前需要它)开机时重新应用
  • 先压平主机,再调传输。 C-state 受限、调速器 performance、一个测量过的锁频、SMT 兄弟核清空、IRQ 与 内存网卡本地。这些都从源头打击抖动。
  • 弄清你的持久化类别。 只有内核引导参数变更需要重启机器;大多数旋钮是即时 sysfs,少数需要重启应用但不必 重启主机。
  • 绝不信任一个你没压测确认过的锁频。 打满所有核,读 Bzy_MHz,按 ≤1% 离散度判定 —— 所设的 scaling_max_freq 可能与实际达到的全核时钟悄悄不同。
  • THP 是两层(系统模式 + JVM 参数),干净地改它意味着两层都动,然后重启 JVM 使运行中的堆真正脱离 THP。
  • 让周期性打断者安静下来。 除了绑核之外,运行 busy-spin agent 的核还需要 nohz_full + rcu_nocbs (无时钟 tick / RCU softirq)、把 IRQ 引开,以及 vm.compaction_proactiveness=0 —— 每一项都消除一个 周期性 的抖动来源,这是一次性绑核做不到的。

关于坐落在这层基础之上的 Aeron 传输旋钮,见 参数参考调优概览