跳转到内容

性能测试报告

比较 rtp2httpdmsd_liteudpxyTVGate 在多频道、同频道多客户端及高码率负载下的 CPU 和内存占用。

测试环境与版本

  • 测试日期:2026-09-06。
  • 主机:Apple M3 Max;Parallels Ubuntu 24.04 虚拟机,16 vCPU、16 GiB 内存。
  • 系统:Linux 6.8.0-138-generic,aarch64;所有程序均为原生 ARM64 执行。
  • 编译器:GCC 13.3.0。rtp2httpd 使用 Release 与 ENABLE_AGGRESSIVE_OPT=ON;msd_lite 使用 -O3、LTO 及相同的内联、循环展开、向量化优化选项;udpxy 使用 -O3 -flto。TVGate 使用官方发布的二进制文件。
  • 组播输入及 HTTP 输出均经过 lo,不修改内核网络参数。net.core.rmem_maxnet.core.wmem_max 均为 212992,TCP 拥塞控制为 cubic。
程序测试版本
rtp2httpdv3.17.0-beta.1
msd_litefa68e131,2026-07-20;liblcb e2f420a2
udpxy31d4bcfa,2026-04-13
TVGatev3.2.0,2026-09-06

测量方法

rtp2httpd 设置 -C -w 1,msd_lite 配置一个事件循环线程,udpxy 保留每个客户端一个子进程的原生模型;这三个程序的全部进程及线程固定在一个 vCPU。TVGate 不设置 GOMAXPROCS,不额外限制 CPU 亲和性,使用虚拟机全部 16 个 vCPU 上的默认多核调度。调度方式不同,结果反映各自配置在指定输入负载下的服务 CPU 成本。

CPU 使用率取完整测量窗口内 /proc/PID/stat 的用户态与内核态时间增量,除以实际墙钟时间,再对整个服务进程树求和。100% 表示占满一个 vCPU,多核程序可以超过 100%。负载发送器、接收器和测量控制进程固定在其他 vCPU,其 CPU 另行记录,不计入服务 CPU;TVGate 的默认调度可使用这些 vCPU。

PSS 和 USS 从 smaps_rollup 每秒采样并对进程树求和。PSS 按比例计入共享页,USS 仅统计私有页,均不包含全部内核 socket 内存或未映射的匿名文件页缓存,因此不能作为服务总内存成本。

每个 RTP 数据报携带 7 个 188 字节 MPEG-TS 空包,共 1316 字节负载。接收端解析 HTTP 分块编码后持续读取流。每轮重启服务与负载进程,所有客户端开始收到数据后再预热,测试逐项串行执行,每轮轮换程序顺序,四轮中每个程序在各个执行位置各出现一次。

msd_lite 保留上游示例的 48 KiB 接收水位、64 KiB 发送水位、1 MiB 环形缓冲;仅适配监听地址、接口、线程数、日志和拥塞控制。udpxy 保留默认缓冲设置。TVGate 仅配置监听端口及 loopback 组播接口,并发、缓冲、连接上限及日志均使用程序默认值。

测试场景

场景客户端数组播源数单源负载码率重复次数每轮预热 / 采样
多频道8840 Mbps45 s / 15 s
同频道 8 客户端8140 Mbps45 s / 15 s
同频道 64 客户端64120 Mbps45 s / 15 s
高码率11400 Mbps45 s / 15 s

测试结果

以下 CPU 和内存统计包含每个场景全部完成采样的轮次,衡量指定负载下的资源占用,不用于认证转发正确性或最大可持续吞吐量。

CPU 使用率

数值为逐轮平均 CPU 的均值。

场景rtp2httpdmsd_liteudpxyTVGate
8 频道,各 40 Mbps🏆 3.69%12.28%23.02%56.65%
8 客户端同频道,40 Mbps🏆 4.87%5.58%30.92%45.09%
64 客户端同频道,20 Mbps🏆 4.78%5.50%59.93%115.08%
单客户端,400 Mbps🏆 12.88%15.46%26.45%57.86%

PSS 内存占用(MiB)

场景rtp2httpdmsd_liteudpxyTVGate
8 频道,各 40 Mbps1.868.95🏆 0.7925.82
8 客户端同频道,40 Mbps1.131.35🏆 0.7922.76
64 客户端同频道,20 Mbps🏆 1.301.374.5546.26
单客户端,400 Mbps1.261.34🏆 0.3219.16

USS 内存占用(MiB)

场景rtp2httpdmsd_liteudpxyTVGate
8 频道,各 40 Mbps1.248.94🏆 0.5225.82
8 客户端同频道,40 Mbps🏆 0.511.340.5222.76
64 客户端同频道,20 Mbps🏆 0.681.353.9646.26
单客户端,400 Mbps0.631.33🏆 0.1219.16

附:rtp2httpd 的性能优化策略

按生命周期分配内存

连接只为实际使用的协议分配 RTSP 或 HTTP 代理状态,FEC 分组表在需要保存恢复分组时才分配。HTTP 输入缓冲和已解析请求使用独立的匿名内存映射:输入缓冲在解析和路由完成后释放,普通媒体流的请求数据在响应头生成后释放。HTTP 代理保留其仍在使用的请求头和请求体。临时请求页可直接交还操作系统,避免长期连接将这些已空闲的页留在堆中。

普通共享组播客户端使用源级重排窗口,不再各自分配闲置的重排数组。RTSP、FCC、截图或 FEC 独立处理路径才分配客户端窗口;流中途启用 FEC 时,已有客户端和后续加入的客户端都会取得各自的窗口。

包缓冲池初始分配 128 个缓冲,按 128 个扩展;控制缓冲池初始分配 16 个,按 16 个扩展。worker 定时回收完全空闲的分段,保留必要的基础容量。客户端队列的逻辑预算与初始预分配量分开计算,减少初始内存不改变原有的缓冲余量。

每个 worker 共享组播订阅

每个 worker 维护一张共享源表,按照解析后的组播地址、端口、SSM 源地址、有效上游接口及 FEC 端口匹配。频道名称、/rtp//udp/ 的路径写法、FCC 服务器参数不参与匹配。相同资源只建立一份主组播 socket,以及按需建立的一份 FEC socket。

订阅源拥有独立的生命周期、超时和重加组定时状态。每个客户端持有订阅引用,最后一个引用释放时关闭 socket 并销毁源状态。最早接入的客户端离开时,事件分发会转交给仍存活的订阅者。各 worker 仍独立管理自己的源表。

这主要减少本机重复 socket 接收、系统调用和应用层处理。多个本机 socket 加入同一个组播组,并不等于上游链路一定传输了相同数量的完整数据流。

共享解析、重排和批次数据

普通组播由共享源统一进行 RTP 解析及重排,然后将有效负载合并到容量为 64 KiB 的批次中。每个 RTP 负载保持完整,在下一个负载会超出容量时先发送当前批次;1316 字节负载对应每批 49 个包、64484 字节。这样既减少每客户端重复解析,也减少逐包分发和发送调用。未满的批次在满 100 ms 后的下一次 worker 定时检查中发送;检查周期为 100 ms,实际延迟还受调度影响。

Buffer 层在原有 1536 字节包缓冲池和控制缓冲池之外,增加按需分配的 64 KiB 批次池。批次池归 worker 所有,已排队的数据可在组播源销毁后继续存活。默认初始分配 4 个批次,按 4 个扩展,最大容量按 buffer-pool-max-size × 1536 字节预算换算,并至少容纳 4 个批次。这是批次池自身的上限,与原有包池分别计量。批次池耗尽时,仍可通过小包引用继续分发。

多个客户端共享底层 payload,各自拥有独立的 buffer_ref_t 视图。视图通过 owner 指向同一份不可修改的数据,并保留自己的链表指针、发送偏移和剩余长度。客户端的部分发送只修改自己的视图;最后一个视图释放后,底层内存才返回池中。每个客户端仍有自己的发送队列和容量限制,慢客户端不会暂停其他订阅者的接收。只有一个订阅者时,直接使用批次自身的描述符,省去额外视图分配。

队列限额改为按底层缓冲容量计费,不能再使用「缓冲数量 × 1536」。一个只剩少量字节未发送的批次,仍占用完整 64 KiB 容量,直到该客户端释放引用。这避免共享大缓冲绕过原有慢客户端内存限制。

减少收发路径的固定开销

支持 recvmmsg 的平台一次接收最多 16 个数据报,worker 复用接收描述符和未消耗的包缓冲。处理完且没有其他引用的包缓冲直接用于下一次接收;重排、FEC 或发送队列仍持有引用时,使用另一个缓冲,避免覆盖待处理数据。数据直接进入缓冲池,不增加一次接收后的复制;不支持批量接收时使用逐包接收。对于已完成起始重排、序号连续且窗口为空的 RTP 流,未启用 FEC 时直接交付负载,省去重排槽位的插入和移除。

主组播 socket 首次就绪时立即读取;后续按实际接收缓冲容量和每次收到的数据报数量,合并最多 1–2 ms 内的接收工作,减少持续小包带来的事件唤醒。系统报告的接收缓冲至少为 128 KiB 时才允许等待 1 ms,至少为 256 KiB 且包量较小时才允许 2 ms;实际间隔还受系统调度影响。延后读取期间暂停该 socket 的就绪通知,读空后重新启用。worker 只遍历仍有待处理接收任务的源,最后一个订阅者离开时同时移除其待处理任务。

接收缓冲较小或无法查询容量时使用水平触发通知。单次读取达到按缓冲容量计算的保守包量阈值时,也恢复即时读取,并按至少 100 ms 的窗口重新估计包速率;速率降低且留有两倍调度余量时再尝试合并,避免一次突发永久关闭优化。水平触发下读到不足一批即可返回,剩余数据仍会通知;延后读取路径则必须确认读空,避免中断造成的短读留下未处理数据。每次回调最多接收 256 个主组播数据报,使持续到来的数据不会长期占用事件循环。这些策略自动选择,无需额外配置。

发送任务先进入 worker 内部队列,只有 socket 暂时无法继续写入时才订阅内核可写事件,减少每个批次的事件监听切换。每个连接单次最多发送 256 KiB,每轮最多处理 128 个发送任务,剩余任务继续排队,使接收、定时器和其他客户端都能得到处理。

状态统计的客户端归属校验使用进程内缓存的 PID,并在每次 fork 后刷新,省去队列更新和发送统计中重复的 getpid() 调用。

队列限额、计数和高水位仍在每次队列操作时更新,向共享状态区发布则集中到 worker 的 100 ms 定时检查中。这样可减少每批数据入队、发送时重复执行的共享内存写入和同步操作;状态页显示最近一次发布的队列快照。

不可修改的批次快照

支持内存文件封存的平台会通过 memfd_create 为共享批次建立匿名内存文件,写入一次后封存,再由多个客户端通过 sendfile 发送同一份内核数据页。只在多个客户端共享接近满容量的批次时采用该路径;普通内存发送仍通过 sendmsg 完成。

每批创建新的文件,发布后禁止写入、增长和截断,不会在缓冲池复用时覆盖旧文件。即使 sendfile 已返回、应用已关闭最后一个文件引用,TCP 仍可能持有旧数据页;文件不可变保证这些待发送内容不会被新一批数据改写。创建、写入或封存失败时保留内存发送;客户端的 sendfile 不受支持时,只将该客户端的视图回退到内存发送。

FCC、FEC 与客户端隔离

FCC 单播和切换状态按客户端独立维护。衔接时先共享组播 socket;等单播和待处理数据完成、重排序号与共享源对齐,再加入共享批次分发。切换前先发送旧批次,避免新订阅者重放之前的内容。

截图处理仍保留独立状态。配置 FEC 端口的源使用共享 socket、每客户端独立的重排及 FEC 恢复路径;运行中首次发现带内 FEC 时,会先发送现有批次,并把共享重排窗口交接给各客户端,再转入独立处理。因而本报告普通组播的 CPU 结果不能直接代表 FCC 补流阶段、FEC 恢复或截图负载。

适用范围

这是 ARM64 Linux 虚拟机内的固定码率转发资源测量,不是物理网卡吞吐上限、视频解码性能或最大可承载客户端数量测试。loopback 内核工作中记在发送器及读流进程上的部分不属于服务 CPU,主机调度也会引入波动。其他硬件、码率、客户端速度、并发频道数量及网络路径应单独测量。

复现测试

脚本及参数见 tools/stress-test/README.md。准备对应版本的二进制文件后,运行四种场景:

bash
scripts/benchmark.sh rtp2httpd msd_lite udpxy tvgate \
  --cases distinct8 shared8 shared64 high400 \
  --repetitions 4 --warmup 5 --duration 15

通过 --binary 名称=路径--revision 名称=版本 指定实际使用的二进制及版本,按上表设置各场景的重复次数和采样时长。CPU、PSS、USS 汇总位于输出目录中的 resources.json。输出目录默认位于 build/benchmark/,测试记录仅在本地保留。

基于 GPL-2.0 许可证发布