Linux 性能调优系列(二十四): 从 CPU 省电到网卡卸载,聊聊 Linux 网络调优
1 | 作者:李晓辉 |
前面两篇,我们分别聊了 TCP 缓冲区和 BDP,以及 MTU 从 1500 提升到 9000 的巨帧优化。
但网络性能调优还有两个容易被忽略的方向:CPU 为了省电,可能增加网络处理的唤醒延迟;网卡驱动和卸载功能的配置,也可能影响吞吐量、CPU 占用和延迟抖动。
今天我们就从这两个方向入手,看看有哪些值得关注的调优手段。
一、CPU 为了省电,为什么反而可能拖慢网络?
先想象一个场景:一台服务器平时负载不高,网络请求也不多,但偶尔会出现几毫秒甚至更高的延迟尖峰。
你检查了网络带宽、TCP 缓冲区和 MTU,似乎都没什么问题。这时候,不妨把目光转向 CPU 的空闲状态管理。
什么是 CPU C-state?
CPU 不仅有运行频率的高低,还有不同的工作状态和空闲状态。
C-state 就是 CPU 的空闲状态管理机制。CPU 暂时没有任务执行时,可以进入不同深度的空闲状态,以降低功耗。
可以简单理解为:
| 状态 | 大致含义 |
|---|---|
| C0 | CPU 正在执行指令 |
| C1 | 浅度空闲,通常能够较快恢复执行 |
| C3 | 更深的空闲状态,可能关闭更多内部单元 |
| C6 及更深状态 | 通常具有更高的节能效果,但退出延迟也可能更大 |
注意,具体有哪些状态、每个状态的功耗和退出延迟,都取决于 CPU 型号及平台实现,并不是每台机器都有相同的 C-state。
CPU 进入深度空闲状态后,能够节省更多电力。但当网卡收到数据,需要 CPU 处理软中断、协议栈和应用任务时,CPU 可能需要先退出空闲状态。
这段退出延迟,在高性能网络和低延迟业务中就可能成为一个影响因素。
例如,高频交易、实时通信、低延迟 RPC 等业务,不仅关注平均延迟,还关注 P99、P99.9 这样的尾延迟指标。即使平均延迟变化不大,偶发的唤醒延迟也可能影响业务体验。
不过,不要一看到延迟尖峰就认定是 C-state 导致的。网卡中断合并、CPU 调度、软中断积压、NUMA 跨节点访问等,同样可能造成延迟抖动。
怎么检查 C-state?
先查看 CPU 空闲状态统计:
安装一下工具
1 | [root@host1 ~]# dnf install kernel-tools -y |
1 | [root@host1 ~]# cpupower monitor |
不同 CPU 平台和内核版本显示的监控项可能不同。执行后重点观察各个空闲状态的驻留时间,而不是只盯着某一个数字。
例如,如果 CPU 大部分时间都处于较深的空闲状态,并且业务又对唤醒延迟非常敏感,就值得进一步测试限制 C-state 后的变化。
如何限制 CPU 的空闲状态?
Linux 可以通过内核参数限制某些空闲驱动允许使用的状态。
但这里要注意:intel_idle 和 acpi_idle 是不同的 CPU 空闲驱动,不能把它们的参数混为一谈。
先查看当前使用的驱动:
1 | [root@host1 ~]# cat /sys/devices/system/cpu/cpuidle/current_driver |
我这里的none这表示当前内核没有报告正在使用的 CPU idle 驱动。
如果输出是 intel_idle,说明当前使用 Intel 的专用空闲驱动;如果输出是 acpi_idle,则使用 ACPI 空闲驱动。
对于使用 intel_idle 的系统,可以考虑通过 intel_idle.max_cstate 限制可使用的空闲状态。例如:
1 | [root@host1 ~]# grubby --update-kernel=ALL --args="intel_idle.max_cstate=1" |
对于使用 acpi_idle 的系统,可以考虑:
1 | [root@host1 ~]# grubby --update-kernel=ALL --args="processor.max_cstate=1" |
这两组参数应根据实际驱动选择,不要不加判断地同时配置。
这里设置为 1,主要是限制空闲状态的深度,并不意味着 CPU 永远处于 C0,也不代表所有平台都会表现为完全相同的 C-state。
修改后需要重启,并验证内核启动参数及实际空闲状态:
1 | cat /proc/cmdline |
生产环境操作前,建议记录原始参数并准备回滚方案。
另外,限制 C-state 往往会增加功耗和发热。对于普通业务服务器,不建议为了追求更低延迟就一律禁用深度空闲状态。
使用 tuned 控制空闲状态延迟
如果希望更灵活地控制 CPU 空闲状态,可以研究 RHEL 提供的 tuned。
先查看当前配置:
1 | [root@host1 ~]# tuned-adm active |
创建一个独立的调优配置:
1 | mkdir -p /etc/tuned/profiles/network-latency-custom |
这里的 force_latency 用于通过 CPU 调优插件约束空闲状态的退出延迟。cstate.id:1 是一种按状态索引指定的方式,但索引对应的具体状态取决于平台,不能直接认定所有机器上的 1 都代表同一个 C-state。
应用之前,先确认当前 RHEL 10 的 tuned 版本支持该配置,并结合实际 CPU 状态验证:
1 | [root@host1 ~]# tuned-adm profile network-latency-custom |
如果配置不被支持,或者状态变化与预期不符,就不要直接将其投入生产。
需要注意,force_latency 不是简单地把 CPU 锁定在某个状态,也不是完全禁止 C-state。它的目标是约束空闲状态退出延迟,最终行为取决于平台和内核实现。
NIC 驱动参数怎么改,而且重启不丢?
CPU 状态看完了,再来看网卡。
同样一张网卡,在不同驱动参数下,可能表现出不同的中断延迟、功耗、吞吐量和 CPU 占用。
先确定网卡使用什么驱动
使用 ethtool -i:
1 | [root@host1 ~]# ethtool -i ens160 |
这里的 vmxnet3 是网卡驱动名称。实际生产环境也可能是 ixgbe、i40e、ice、bnxt_en 或虚拟网卡驱动 virtio_net、vmxnet3 等。
不同驱动支持的参数完全不同。
接下来可以使用 modinfo 查看模块参数:
1 | modinfo -p vmxnet3 |
例如,某些驱动会提供中断延迟、调试或节能相关参数。但不要直接照搬其他驱动的配置:参数名称、取值范围、默认值和适用硬件都必须以当前驱动为准。
对于虚拟机,还需要注意:来宾操作系统看到的可能是虚拟网卡,物理网卡驱动参数则由宿主机或云平台管理。
如何持久化驱动模块参数?
假设当前驱动确实支持某个名为 ExampleParam 的参数,可以在 /etc/modprobe.d/ 中建立配置文件:
1 | cat > /etc/modprobe.d/example-net.conf <<'EOF' |
上面是演示语法,example_driver 和 ExampleParam 需要替换为真实驱动及其支持的参数,不能原样照抄执行。
这种方式适用于通过模块加载机制传入的参数。并不是所有网卡设置都属于模块参数,也不是所有参数都能在模块加载后修改。
修改后,应该先确认参数的生效方式。某些参数需要重新加载驱动,甚至需要重启系统。
特别提醒:不要在远程 SSH 会话中贸然执行 modprobe -r 卸载正在使用的网卡驱动。 如果 SSH 就是通过这张网卡连接的,卸载驱动可能直接切断连接;生产服务器应使用带外管理、云控制台等可靠的恢复途径。
如果确实需要重新加载驱动,必须先确认没有其他网卡或关键业务依赖该模块,并安排维护窗口。
网卡卸载功能:让硬件替 CPU 干活
最后来看一个更常见、也更容易实测的优化方向:网卡卸载。
现代网卡可以替 CPU 完成部分网络处理工作,例如 TCP 分段、校验和计算以及接收数据包合并。
这些功能可以减少 CPU 在逐包处理上花费的时间,尤其是在高吞吐量场景下。
先查看当前卸载状态
使用 ethtool -k:
1 | [root@host1 ~]# ethtool -k ens160 |
重点关注这些项目:
1 | tcp-segmentation-offload: on |
这里的输出仅用于说明常见项目,实际结果取决于网卡和驱动。
几个常见功能的作用如下:
| 功能 | 作用 |
|---|---|
| TSO | 发送端将较大的 TCP 数据交给网卡分段 |
| GRO | 接收端合并符合条件的数据包,减少协议栈处理次数 |
| TX checksum offload | 由网卡协助计算发送方向的校验和 |
| RX checksum offload | 由网卡协助验证接收方向的校验和 |
举个例子:如果应用要发送大量 TCP 数据,启用 TSO 后,内核可以把较大的数据块交给网卡,由网卡完成后续分段。
这样,CPU 不必亲自处理每一个线上 TCP 段,有机会降低处理开销。
但要注意,TSO、GRO 等机制与 MTU 是不同维度的优化。即使 MTU 仍然是 1500,网卡也可能通过卸载功能减少 CPU 负担。
如何临时修改卸载功能?
例如,临时关闭 TSO:
1 | [root@host1 ~]# ethtool -K ens160 tso off |
重新开启:
1 | [root@host1 ~]# ethtool -K ens160 tso on |
查看当前状态:
1 | [root@host1 ~]# ethtool -k ens160 | grep -E 'tcp-segmentation-offload|generic-receive-offload|tx-checksumming|rx-checksumming' |
这些设置通常用于临时测试或故障排查。
例如,如果怀疑某个驱动的卸载功能存在兼容性问题,可以在可控环境中关闭对应功能,比较修改前后的吞吐量、CPU 使用率和错误计数。
但不要为了“优化”就把所有卸载功能全部关闭。很多情况下,这样反而会增加 CPU 开销、降低吞吐量。
如何让卸载配置持久生效?
通过 ethtool -K 修改的功能状态通常不会自动成为永久配置。要持久化,可以使用 NetworkManager 支持的连接配置属性。
先查找网卡对应的连接配置名称:
1 | [root@host1 ~]# nmcli connection show |
假设连接配置名称确实是 ens160,并且当前 NetworkManager 版本支持 TSO 对应的 ethtool 属性,可以尝试:
1 | [root@host1 ~]# nmcli connection modify ens160 ethtool.feature-tso on |
然后重新激活连接:
1 | [root@host1 ~]# nmcli connection down ens160 && nmcli connection up ens160 |
最后验证:
1 | [root@host1 ~]# ethtool -k ens160 | grep tcp-segmentation-offload |
这里有个细节:连接配置名称不一定等于网卡设备名,而且 NetworkManager 并非在所有版本中都支持相同的 ethtool.feature-* 属性。
如果命令提示属性不支持,应先检查本机版本的 nmcli 帮助和 NetworkManager 文档,不要直接认定这是 RHEL 10 所有环境通用的语法。
此外,持久化配置最终能否生效,还取决于驱动是否支持该功能,以及 NetworkManager 是否成功应用了连接配置。
怎么判断调优真的有效?
这篇文章涉及的三个方向,最终都应该通过测试验证,而不是仅仅看配置是否修改成功。
建议每次只调整一个变量,并在相同的负载、网卡速率和测试时长下进行对比。
可以重点观察:
- 吞吐量: 使用
iperf3对比网络传输能力。 - CPU 使用率: 观察完成相同传输任务所需的 CPU 资源。
- 延迟分布: 关注平均延迟以及 P95、P99 等尾延迟。
- 中断与软中断: 结合
/proc/interrupts、/proc/softirqs观察变化。 - 功耗与温度: 特别是限制 C-state 后,要确认额外的能耗是否值得。
例如:
1 | # 查看网卡中断分布 |
如果要验证 CPU 空闲状态对延迟的影响,可以在业务负载尽量一致的情况下,比较调优前后的延迟分布,而不是只执行一次 ping 就下结论。
对于 NIC 卸载功能,也应优先在测试环境中验证,确认没有吞吐量下降、丢包或兼容性问题后,再考虑推广到生产环境。
总结
这篇我们主要关注三个方向:
- CPU C-state: 深度空闲状态可能增加唤醒延迟,但是否影响业务,需要结合 CPU 平台和延迟测试判断。
- NIC 驱动参数: 使用
ethtool -i和modinfo -p了解驱动及其参数,再根据驱动支持情况选择持久化方式。 - 网卡卸载功能: 使用
ethtool -k查看状态、ethtool -K进行临时测试,确认 NetworkManager 支持后再配置持久化。
网络调优不是把所有参数都调到最大,也不是把所有省电功能都关掉。
先找到瓶颈,再一次调整一个参数,最后用吞吐量、CPU 占用和尾延迟验证效果。 这才是生产环境中更可靠的调优方法。
