Nginx sendfile 零拷贝需与tcp_wmem等内核参数协同调优,否则性能反降;须匹配网卡TX ring大小、开启tcp_nopush、同步调整wmem_default/max,并通过ss、/proc/net/snmp和perf验证效果。

Nginx 的 sendfile 是零拷贝关键机制,但它不是“开个开关就自动跑满带宽”的功能——必须和内核 TCP 发送缓冲区(tcp_wmem)协同,否则容易出现“数据推不出去”或“推得太碎”,反而拖慢传输效率。
sendfile 要生效,得让内核有足够且合适的发送空间
sendfile 从文件系统直接把数据送进 socket 发送队列,跳过用户态内存拷贝。但最终能否高效发出去,取决于内核为每个 socket 分配的 TCP 发送缓冲区大小:
-
net.ipv4.tcp_wmem = 4096 65536 4194304(min default max)- 中间值(64KB)是内核默认为每个 socket 分配的发送缓冲区大小
- 这个值应 ≥ Nginx 的
sendfile_max_chunk(若设了),且 ≤ 网卡 TX ring 缓冲区深度(常见为 256–1024 个描述符 × 每个约 16KB) - 若后端是万兆网卡、TX ring 设为 512,建议将
tcp_wmem中间值调至 262144(256KB),匹配一次 sendfile 推送的大块数据
-
net.core.wmem_default和net.core.wmem_max也要同步放大-
wmem_default应等于或略大于tcp_wmem中间值,避免被覆盖 -
wmem_max建议设为tcp_wmem最大值(如 4MB),给大文件传输留余量
-
tcp_nopush 必须配合 sendfile 使用
tcp_nopush on 不是可选项,而是联动前提:
- 它会让内核等到 TCP 缓冲区填满(或遇到 FIN/flush)再发包,避免小包堆积
- 若只开
sendfile却关tcp_nopush,内核可能把一个大文件切成几十个 1–2KB 小包发出去,ACK 风暴+中断飙升
实际验证是否对齐成功
- 用
ss -i查看某连接的snd_wnd(发送窗口)和wscale(窗口缩放因子),确认实际窗口 ≥ 256KB - 用
cat /proc/net/snmp | grep Tcp:观察TcpOutSeg数量;在大文件下载场景下,该值应明显低于未优化时(说明合并发包效果好) -
perf record -e syscalls:sys_enter_sendfile可确认sendfile系统调用是否高频触发且返回字节数稳定(如每次 256KB)
别忽略硬件层约束
-
ethtool -g eth0查看网卡 RX/TX ring 大小,若 TX ring 只有 256,tcp_wmem再大也推不进硬件队列 - 启用
tx offload(如tso,gso)并确保驱动支持,才能让单次sendfile推送的数据被网卡自动分段
sendfile 和 tcp_wmem 是软硬交界处的关键配合点,调错一个参数,零拷贝就退化成“伪零拷贝”。


















