内核4.14中MSG_ZEROCOPY/SO_ZEROCOPY性能劣于非零拷贝的原因问询
关于4.14内核下Squid MSG_ZEROCOPY测试性能反降的问题分析
我之前在类似的缓存代理场景踩过MSG_ZEROCOPY的坑,也接触过其他开发者的同类测试案例,结合你的描述和CPU热点数据,大概能梳理出问题的核心原因:
1. 频繁EPOLLERR唤醒的本质不是错误,是零拷贝的正常机制
你遇到的EPOLLERR唤醒其实是内核给MSG_ZEROCOPY的完成通知,不是真的套接字错误。MSG_ZEROCOPY的设计要求:每次调用send(fd, buf, sz, MSG_ZEROCOPY)后,必须通过recvmsg(fd, &msg, MSG_ERRQUEUE)来获取数据发送完成的反馈,释放内核中临时映射的缓冲区。
如果没及时处理这个通知,内核会持续触发epoll事件,导致你的进程被高频唤醒——看你的zero_copy CPU数据,ep_send_events_proc、do_syscall_64这类epoll和系统调用相关函数占比极高,就是这个原因。很多开发者一开始误以为这是错误事件,处理逻辑没跟上,直接导致CPU占用飙升。
2. 零拷贝性能反降的核心原因
你的测试结果和文档不符,主要和数据块大小以及系统调用开销有关:
- 小数据块放大了零拷贝的Overhead:你每次send的大小是16KB/32KB,而MSG_ZEROCOPY本身存在内核页表映射、完成通知的系统调用等固定开销。对比非零拷贝的
copy_user_enhanced_fast_string——这是内核优化到极致的用户态到内核态拷贝路径,小数据块场景下,拷贝的成本反而比零拷贝的额外流程更低。其他开发者的测试也验证了这一点:只有当send块大小调到256KB以上时,零拷贝的优势才会体现出来。 - 高频系统调用叠加开销:零拷贝流程下,每次send都要对应一次recvmsg的系统调用,相当于两次syscall;而非零拷贝可能一次send就完成数据传输。在你400并发的场景下,这种syscall次数的差异被放大,直接拉高了sys和us的CPU占比。
3. 其他开发者的同类测试情况
确实有不少开发者在Squid或类似反向/正向代理场景测试过MSG_ZEROCOPY,比如技术社区的讨论、内核邮件列表的案例中,很多人都遇到过和你一样的问题:小block size下零拷贝性能不如传统拷贝,以及未正确处理MSG_ERRQUEUE导致CPU过高的情况。后来通过调整send块大小、批量处理完成通知等优化,才让零拷贝的性能反超非零拷贝。
可以尝试的优化方向
- 调大send的块大小:比如从16KB/32KB调整到256KB~1MB,减少系统调用次数,抵消零拷贝的固定开销。
- 批量处理MSG_ERRQUEUE通知:不要每次send后立即调用recvmsg,而是攒一批epoll事件后批量处理完成通知,减少上下文切换的次数。
- 核对内核配置:确认是否开启了透明大页等有助于零拷贝的内核特性,避免其他配置拖慢性能。
内容的提问来源于stack exchange,提问作者zengxiaobai2
相关产品推荐
相关产品推荐

