关闭TSO/GSO后iperf单流无法跑满带宽,是因CPU软中断瓶颈与TCP并发粒度失配所致:单流受限于RTT和窗口,多流(如-P5)可摊薄开销但过多会引发软中断争抢、锁竞争和cache颠簸。

为什么关了TSO/GSO,iperf跑不出满带宽?
这不是命令写错了,而是你正踩在「CPU软中断瓶颈」和「TCP并发粒度失配」的交界点上。典型现象是:单流 iperf -P1 仅跑出 2~3Gbps(10G网卡),但开到 -P5 才接近峰值,再加进程反而回落——这恰恰说明关闭卸载后,内核协议栈成了新瓶颈。
- TSO/GSO 关闭后,原本由网卡硬件完成的分段任务全压给 CPU:每个大包都要在
dev_hard_start_xmit()中被切片、填 TCP/IP 头、计算校验和,消耗大量 cycles - 单 TCP 流受限于 RTT 和接收窗口,无法打满物理带宽;而多流能摊薄单流调度开销,但超过临界点(如 -P5)后,软中断争抢、队列锁竞争、cache line bouncing 开始反噬吞吐
- 尤其在 Docker 容器中,veth pair + bridge 转发路径叠加 GSO 禁用,会导致 skb 在 host namespace 和 container namespace 间反复克隆+重分片,放大延迟
哪些业务场景下关 Offload 反而伤性能?
不是所有“低延迟需求”都适合关 TSO/GSO。真实受损场景往往具备以下特征:
- 高吞吐微服务网关:Envoy/Nginx 作为 sidecar,每秒处理数万连接,且多数请求体小(
- 实时风控/交易撮合系统:依赖低 P99 延迟,但流量模型是「少量大包 + 大量心跳小包」。关 TSO 后,大包分片阻塞 TX 队列,小包排队等待时间从 µs 级跳到 ms 级
-
Kubernetes HostNetwork Pod:容器直接使用宿主机网络栈,但
ethtool -K只作用于物理口,veth peer 的 GSO 状态未同步,造成发送路径 GSO off(host)→ on(container)不一致,触发内核 fallback 到 GSO soft path,性能断崖式下跌
怎么验证是不是 Offload 关得不对?
别只看 ethtool -k eth0 显示 “off”,要确认它真在生效路径上:
- 查实际发送路径是否绕过 GSO:
cat /proc/net/snmp | grep -A1 Tcp看TcpOutSegs是否远高于TcpOutDataSegs(差值大说明分段发生在协议栈,GSO 生效);关闭后两者应趋近 - 抓包对比:
tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn|tcp-fin) != 0'若 SYN 包长度 > 1500 字节,说明 TSO 仍生效(常见于没对 veth 或 docker0 桥接设备执行相同操作) - 检查驱动层是否静默忽略:
ethtool -i eth0查driver名,某些旧版 ixgbe/igb 驱动在禁用 TSO 后会 fallback 到 LRO/GRO 行为,需一并关掉gro off lro off
真正该关 Offload 的时候,只关一部分
全关 TSO/GRO 是懒办法。生产环境更推荐「按需裁剪」:
- 只关
tso off,保留gso on:让内核在协议栈做通用分段,避免硬件兼容性问题,同时保持分段逻辑可控 - 关
gro off但开lro off:GRO 在软中断中聚合,易干扰实时性;LRO 在驱动层合并,影响更小,部分智能网卡还支持可编程 LRO 规则 - 对容器网络,必须同步操作:
ethtool -K docker0 gso off+ethtool -K vethXXXX gso off,否则 veth pair 两端 GSO 状态错配会触发 skb_linearize() 强制拷贝,CPU 直接翻倍
最常被忽略的一点:关 Offload 不等于解决延迟,它只是把问题从网卡 queue 移到了 CPU softirq queue。如果你没调优 /proc/sys/net/core/netdev_budget、没绑定 irq 到专用 core、没限制容器 CPU quota,那关了也是白关。










