-
Notifications
You must be signed in to change notification settings - Fork 255
AI给的性能优化建议 #439
Description
通过比对msd_lite和rtp2httpd的性能差距得到的结果
ai:
通过对比 rtp2httpd 和 msd_lite 的源代码,可以明显看出两者在架构设计和数据转发策略上的核心差异。
msd_lite 具有极低 CPU 负载的核心秘诀在于其**“内核级零拷贝(Zero-Copy)”与大块数据处理**机制(它将接收到的数据写入基于临时文件 /tmp/msd-*.tmp 的环形缓冲区,然后直接调用系统的 sendfile() 函数将数据从内核态直接发送给所有客户端)。
相比之下,rtp2httpd 在用户态进行了大量精细的包级别(Packet-by-packet)处理。为了降低 rtp2httpd 的 CPU 负载,可以从以下几个关键方向进行优化:
1. 禁用或优化 MSG_ZEROCOPY 的使用(关键优化点)
在 rtp2httpd 的 worker.c 中,可以看到其尝试使用 Linux 的 MSG_ZEROCOPY 特性(zerocopy_handle_completions)。
- 问题所在:Linux 官方内核文档明确指出,
MSG_ZEROCOPY会带来页面锁定(Page pinning)和处理完成通知(Error queue)的额外开销。它仅在发送大块数据(通常大于 10KB - 64KB)时才能带来性能提升。由于组播的 RTP/UDP 数据包通常在 1316 字节左右(MTU 限制),在这个级别使用MSG_ZEROCOPY不仅不会降低 CPU 使用率,反而会因为频繁的上下文切换和完成通知处理导致 CPU 负载显著升高。 - 优化建议:
- 对于标准大小的 RTP 数据包转发,默认回退到普通的
send()或writev()。 - 只有当在用户态将多个包聚合(Batching)成超过 10KB 以上的大块缓冲后,才考虑开启
MSG_ZEROCOPY。
- 对于标准大小的 RTP 数据包转发,默认回退到普通的
2. 使用 recvmmsg 替代 recv 批量收包
在 rtp2httpd 的 multicast.c 的 mcast_session_handle_event 函数中:
int actualr = recv(session->sock, recv_buf->data, BUFFER_POOL_BUFFER_SIZE, 0);- 问题所在:这里虽然使用了一个
for(;;)循环来排空边缘触发(EPOLLET)的缓冲区,但每次recv只能读取一个 UDP 数据报。对于高码率的 IPTV 流(如 10Mbps - 20Mbps),每秒会产生几千到上万次的recv系统调用(Syscall),这会极其消耗 CPU。 - 优化建议:
- 使用 Linux 的
recvmmsg()(Receive multiple messages)系统调用替换recv()。 recvmmsg()允许一次系统调用将多个 UDP 数据包接收到一个iovec数组中,能将系统调用次数减少一个数量级,直接大幅降低内核态 CPU 开销。
- 使用 Linux 的
3. 减少单包级别的内存分配与队列管理
在 multicast.c 的收包循环中:
buffer_ref_t *recv_buf = buffer_pool_alloc();
// ...
int processed_bytes = stream_process_rtp_payload(ctx, recv_buf);- 问题所在:
rtp2httpd为每一个进来的 UDP 数据包分配(哪怕是从对象池获取)一个buffer_ref_t,然后经过解析后加入到重排队列,最后又加入到每个客户端的发送队列。这种高度碎片化的内存管理极易导致 CPU 缓存未命中(Cache Miss)和队列锁/管理开销。 - 优化建议:借鉴
msd_lite的环形缓冲区(Ring Buffer)设计。不需要为每个包分配单独的结构体,而是预分配一块巨大的连续内存(例如 1MB 的大 Buffer),将收到的 Payload 直接连续 append 到大缓冲中。客户端维护一个读取游标(offset),使用writev()或send()一次性发送尽可能多的连续数据块。
4. 提供“直通模式”跳过 RTP 重排(RTP Reordering)
在 stream.c 的 stream_process_rtp_payload 中:
return rtp_reorder_insert(&ctx->reorder, buf_ref, seqn, ctx->conn, ...);- 问题所在:所有收到的 RTP 数据包都要去解析 Header 获取 Sequence Number,然后插入到重排缓冲区(Reorder Buffer)中。排序操作对于每个包都需要消耗 CPU。
- 优化建议:
- 绝大部分局域网或运营商内网组播是非常稳定的,基本不存在乱序(除非开启了 FEC 需要修复丢包)。
- 建议在配置中增加一个 Passthrough(直通)模式。在不需要做 FCC(快速切台)和 FEC(前向纠错)的时候,跳过 RTP 头的解析和
rtp_reorder_insert排序过程,直接将收到的裸数据推入发送队列,这能省下大量 CPU 周期。
5. 聚合发送(Write Batching)
在 worker.c 处理事件时,只要客户端套接字可写(POLLER_OUT),或者有新数据到来,就会尝试向客户端发送数据。
- 优化建议:不要在每次收到一个组播包时就唤醒去给几十个 HTTP 客户端执行发送。可以引入一个非常短暂的微小延迟(例如 5ms - 10ms)进行聚合发送(类似 Nagle 算法的思路)。每 10ms 收集到的媒体数据可能有几十上百 KB,此时再调用一次
write()一次性发给客户端,可以极大地减少发送端的 Syscall 数量。
总结:
rtp2httpd 的高 CPU 负载主要是由于**频繁的 Syscall(每包 recv、每小包 zerocopy 发送)以及逐包的用户态逻辑(分配、解包、重排序)**引起的。通过引入 recvmmsg、聚合发送、禁用小包 MSG_ZEROCOPY 以及增加免重排的直通模式,可以使其性能大幅接近 msd_lite。