Skip to content

AI给的性能优化建议 #439

@asiacny

Description

@asiacny

通过比对msd_lite和rtp2httpd的性能差距得到的结果

ai:
通过对比 rtp2httpdmsd_lite 的源代码,可以明显看出两者在架构设计和数据转发策略上的核心差异。

msd_lite 具有极低 CPU 负载的核心秘诀在于其**“内核级零拷贝(Zero-Copy)”与大块数据处理**机制(它将接收到的数据写入基于临时文件 /tmp/msd-*.tmp 的环形缓冲区,然后直接调用系统的 sendfile() 函数将数据从内核态直接发送给所有客户端)。

相比之下,rtp2httpd 在用户态进行了大量精细的包级别(Packet-by-packet)处理。为了降低 rtp2httpd 的 CPU 负载,可以从以下几个关键方向进行优化:

1. 禁用或优化 MSG_ZEROCOPY 的使用(关键优化点)

rtp2httpdworker.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

2. 使用 recvmmsg 替代 recv 批量收包

rtp2httpdmulticast.cmcast_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 开销。

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.cstream_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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions