主机与操作系统延迟参数
参数参考讲的是 Aeron 的 传输 旋钮。本页讲的是其下的一层:决定那些传输旋钮 能否运行在一个安静、可预测的核上的 主机、操作系统与 JVM 设置。几乎每一个在缓冲区正确取值后仍然存在的 毫秒级尾延迟,最终都能追溯到这一层的某个原因 —— 某个核掉进了深度 C-state、时钟在负载下回落、khugepaged 正在压缩大页,或一个 IRQ 落到了远端 NUMA 节点上。
如何阅读这些表
Section titled “如何阅读这些表”对每一个系统旋钮,除了它的延迟影响之外,还有两件事至关重要:它住在哪里,以及 它能持续多久。人们最常 搞错的就是持久化模型。
| 持久化类别 | 通过什么设置 | 重启后保留? | 说明 |
|---|---|---|---|
| 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 它们 —— 但需要 重启进程(而非重启机器)才能生效。 |
CPU 功耗:C-state、P-state、频率
Section titled “CPU 功耗:C-state、P-state、频率”在一台看似空闲的机器上,µs 到 ms 级抖动的最大单一来源就是 CPU 切换功耗状态。已经掉进深度 idle 状态的核, 唤醒要花数微秒;按需爬坡的时钟,给每一个安静间隙之后的首条消息都加上方差。
| 参数 | 吞吐 | p50 | p99(尾部) | 通过什么设置 | 持久 | 回退 |
|---|---|---|---|---|---|---|
C-state(intel_idle.max_cstate=1、processor.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 / Boost(intel_pstate/no_turbo、cpufreq/boost) | 更高的单核峰值 | 不适用 | Turbo 抬高 峰值但 增加 方差;关闭则锁在基频(更低,但死平均匀) | sysfs | ❌ 即时 | 重新开启(no_turbo=0 / boost=1) |
陷阱:单核 Turbo ≠ 全核频率
Section titled “陷阱:单核 Turbo ≠ 全核频率”数据手册上的「最大 Turbo」(如 3.8–4.5 GHz)是一个 单核 数字 —— 只有在大多数核空闲、整个封装有热/功耗 余量时才可达。一旦你把 所有 核都打满,硬件就强制大家回落到更低的 全核 Turbo 档。所以「最大 GHz」与 「负载下的全核同频」是互斥的:你无法让每个核同时跑在单核峰值。
做延迟工作,你几乎总是要 同频优先于最大值。一个在所有核上都平的时钟 —— 哪怕值更低 —— 能消除一整类跨核 方差。配方如下:
- 解除任何锁频,让机器在满载下找到它 天然的全核档(测量它 —— 见下一节)。
- 把
scaling_min_freq = scaling_max_freq锁在那个测得的档 上或略低于它,使任何核都无需回落。
确认全核同频(检测 + 压测)
Section titled “确认全核同频(检测 + 压测)”这是人们会跳过的验证步骤 —— 然后一台「已锁频」的机器悄悄地在各核上跑着三个不同的时钟。方法是:把每个核打到 100% 繁忙,然后读取实际的逐核频率并检查离散度。
第 1 步 —— 读取所设的锁频
Section titled “第 1 步 —— 读取所设的锁频”# 策略被设成了什么(每个唯一的 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-pstatecat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor # 预期:performancecat /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):
# 首选 —— 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 & doneturbostat --interval 1 --num_iterations 10 --quietpkill yes # 之后务必停掉负载从输出里读两样东西:
- 汇总行(CPU 列为
-)给出 全核平均Bzy_MHz。 - 每个逐核行给出该核的
Bzy_MHz。在各核之间计算 min / avg / max。
第 3 步 —— 套用离散度判据
Section titled “第 3 步 —— 套用离散度判据”若 (max − min) / avg ≤ 约 1% 则视为平| 观测到的现象 | 判定 |
|---|---|
| 所有核彼此、以及与锁频值都在约 1% 以内 | ✅ 确实全核同频 —— 锁频被遵守 |
| 负载下少数核低于其余核 | ⚠️ 锁频高于全核档 → 把锁频降到回落的下限 |
每个核都跑在所设 scaling_max_freq 之下 | 🔴 硬性 SKU 上限 —— 全核档低于你的设定;按实际达到值锁频,而非按所设值 |
| 仅前 1–2 个采样离散,随后收敛 | 冷机的爬坡滞后,而非真实差异 —— 判断前先热身 ≥5 秒 |
这同一个循环也是你 发现 天然全核档的方法:解除锁频运行(scaling_max_freq = cpuinfo_max_freq),读取
持续的全核 Bzy_MHz,然后锁在它之上或略低处。
CPU 拓扑与调度
Section titled “CPU 拓扑与调度”| 参数 | 吞吐 | p50 | p99(尾部) | 通过什么设置 | 持久 | 回退 |
|---|---|---|---|---|---|---|
| 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 上移除周期性调度 时钟 tick 与 RCU 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 irqbalance | ✅ | systemctl enable --now irqbalance |
kernel.numa_balancing | 不适用 | 消除页迁移扫描成本 | 阻止自动 NUMA 页迁移停顿热线程 | sysctl / 99-*.conf | ✅(文件) | 改回 1 |
内存:大页与 THP
Section titled “内存:大页与 THP”| 参数 | 吞吐 | p50 | p99(尾部) | 通过什么设置 | 持久 | 回退 |
|---|---|---|---|---|---|---|
| 透明大页(THP)模式 | 大堆上更少 TLB 未命中 | 大页支撑的堆略低 | always 让 khugepaged 进程级压缩页 → 周期性停顿;madvise 更温和;never 最确定 | /sys/kernel/mm/transparent_hugepage/{enabled,defrag}(sysfs) | ❌ 即时(或 GRUB transparent_hugepage=never) | echo 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 | ✅(文件) | 默认 |
网络与 socket 缓冲区
Section titled “网络与 socket 缓冲区”这些与传输层的 接收路径缓冲区 有重叠;这里讲的是限制应用可申请上限的 操作系统级别参数。
| 参数 | 吞吐 | p50 | p99(尾部) | 通过什么设置 | 持久 | 回退 |
|---|---|---|---|---|---|---|
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 | ❌ 即时 | 先前的环大小 |
牵动尾部的 JVM 参数
Section titled “牵动尾部的 JVM 参数”| 参数 | 作用 | 持久 |
|---|---|---|
-XX:+UseZGC(或一个低停顿收集器) | 亚毫秒级 GC 停顿 → 消除 GC 对尾部的贡献 | 进程参数 |
-XX:+AlwaysPreTouch | 启动时触碰整个堆 → 运行期不再有首次触碰缺页停顿(与大页配合) | 进程参数 |
-XX:+UseTransparentHugePages | 把堆 madvise 进 THP —— 何时该把它 关掉 见上文 THP 说明 | 进程参数 |
| JIT 预热 | 上线前用代表性流量跑遍所有热路径 —— 冷代码 + 首次触碰会造成严重的早期尖峰。见 预热 JVM | 运行期流程 |
任何跨主机的延迟测量(单向分段计时、相位对齐的突发)都要求各主机共享一个亚微秒级的时间基准,否则你的数字 就是噪声。
| 参数 | 为何重要 | 通过什么设置 | 持久 |
|---|---|---|---|
PTP / 带硬件 PHC 的 chrony | 把每台主机校准到一个共同的 ns 级参考(例如网卡的 PTP 硬件时钟)→ 诚实的单向分段计算与可行的挂钟突发对齐 | 带 refclock PHC 的 chronyd | ✅ 服务 |
持久化与回退速查表
Section titled “持久化与回退速查表”| 层 | 需要重启机器? | 需要重启应用? | 重启后丢失,除非…… |
|---|---|---|---|
C-state、P-state 驱动模式、isolcpus、nohz_full、rcu_nocbs | ✅(引导参数) | 否 | —(GRUB 持久) |
| 调速器、频率锁定、Turbo、THP sysfs、IRQ 亲和性、网卡环、SMT | 否 | 否 | 由 systemd unit / tuned 配置重新应用 |
sysctl 值(rmem_max、swappiness、numa_balancing、compaction_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—— 每一项都消除一个 周期性 的抖动来源,这是一次性绑核做不到的。
本站与 Adaptive Financial Consulting Limited 或 Aeron 项目无任何关联,未获其背书或赞助。 Aeron 是 Adaptive Financial Consulting Limited 的注册商标。
Aeron 是 Adaptive Financial Consulting Limited 在英国及其他国家/地区的商标。