sendfile 能显著降低 nginx 静态文件处理的 cpu 开销,核心是内核态直传、免两次 cpu 拷贝与上下文切换;但必须配套 tcp_nopush(且 tcp_nodelay off),并满足文件类型、响应未被修改、keep-alive 等条件才能生效。

sendfile 能显著降低 Nginx 处理静态文件时的 CPU 开销,核心在于让数据全程留在内核态搬运,跳过用户空间参与——不读进内存、不调用 CPU 拷贝、不触发额外上下文切换。
绕过用户态,省掉两次 CPU 拷贝
普通 read/write 流程中,Nginx 需先 read() 把文件从页缓存复制到用户态缓冲区,再 write() 从该缓冲区复制到 socket 发送队列。这两次拷贝都由 CPU 执行,且伴随两次用户态与内核态切换。而 sendfile 直接在内核中完成“磁盘 fd → socket fd”的搬运,CPU 不搬运数据,只发一次系统调用指令。
- 数据始终在内核页缓存和网络协议栈间流动,无需分配用户态缓冲区
- 单次 sendfile 系统调用替代 read + write 组合,减少 50% 以上系统调用次数
- CPU 使用率中 sy(系统态)占比明显下降,尤其在高并发小文件或大文件场景下
必须配套 tcp_nopush 才能释放全部收益
tcp_nopush 不是可选优化,而是 sendfile 的生效前提之一:它依赖 sendfile 提供的“整块连续数据视图”,才能把响应头和文件起始内容合并进一个 TCP 报文发送。若未开启,内核只能按默认 MSS 分段发包,导致小包泛滥、软中断激增,反而抵消零拷贝优势。
- 单独开 sendfile on 但没配 tcp_nopush,Nginx 日志会提示 “tcp_nopush is ignored”
- tcp_nopush 仅在 sendfile 生效路径下激活;普通 read/write 模式下该选项无效
- 需同步设 tcp_nodelay off,避免 Nagle 算法强行拆包,破坏“攒整包”逻辑
实际生效有硬性运行条件,不满足就自动降级
sendfile 不是配置开关一开就永久生效,而是在每次请求处理时动态判断是否满足全部前提。任一不满足,Nginx 就悄悄回退到低效的 read/write 路径,CPU 开销立刻回升。
- 源文件必须是 open() 打开的真实磁盘文件(如 /var/www/logo.png),不支持管道、/proc、tmpfs 或 NFSv3
- 响应不能被修改:gzip、sub_filter、etag、expires、proxy_buffering 等模块启用即禁用 sendfile
- 连接需保持活跃(keep-alive),否则 page cache 命中率下降,内核可能拒绝零拷贝
- 可通过 strace -e trace=sendfile64,read,write -p $(pgrep nginx) 实时验证是否真在调用 sendfile64
小文件要节制使用,避免 syscall 开销反超收益
对小于 1KB 的图标、JS、CSS 文件,频繁调用 sendfile 可能因系统调用本身开销(上下文切换、参数检查)抵消零拷贝优势。此时应换策略控制粒度:
- 单次 sendfile 传输建议不低于 4KB;更小文件优先用 writev() 合并多个资源一次性发送
- 启用 TCP_CORK(即 tcp_nopush on 的底层机制),让内核暂存小数据,等凑够 MSS 或显式取消再发
- 非阻塞 socket 下需检查 sendfile 返回值,EAGAIN 须轮询或重投事件,不可忽略











