diff --git a/CONCURRENCY_BENCHMARK.md b/CONCURRENCY_BENCHMARK.md new file mode 100644 index 0000000..ff60770 --- /dev/null +++ b/CONCURRENCY_BENCHMARK.md @@ -0,0 +1,187 @@ +# Wrapper Manager 并发性能实测 + +本文记录 `wrapper-manager` v0.2.0 在两台 x86-64 主机上的隔离并发测试,重点回答以下问题: + +1. manager 的连接池是否真的达到配置并发; +2. 增加每个 wrapper 的并发流,吞吐能扩展到什么位置; +3. 当前每个 wrapper 10 路并发是否仍是合理的生产默认值。 + +## 结论摘要 + +- **连接池确实生效。** 两台机器上,每个档位都观察到与目标完全一致的 decrypt `ESTABLISHED` 连接数;没有 pool failure、manager 重启或 wrapper 异常退出。 +- **吞吐上限主要由 wrapper 所在主机决定,不是 manager 的 gRPC `ClientConn` 数量决定。** z16 的最佳中位吞吐为 95.203 MiB/s,rog 为 275.147 MiB/s,后者约为前者的 2.89 倍。 +- **两台机器的生产甜点值均为 10 streams / wrapper。** + - z16 在 pool 8 后进入平台,pool 10 保留了混合负载余量;继续提高到 12 或 16,吞吐几乎不变。 + - rog 的 pool 10 达到全矩阵最佳中位吞吐的 99.07%,pool 12 只再提升 0.94%;pool 16 反而下降 31.26%,波动显著增大。 +- **不建议提高全局默认值到 12 或 16。** 更高并发会增加 wrapper 内部 CPU、IPC、内存复制和调度争用,无法稳定提高有效吞吐。 +- **多开 gRPC `ClientConn` 没有改善上限。** z16 的 pool 16 / 4 ClientConn 比单 ClientConn 慢 5.55%;rog 上慢 14.30%。 + +因此,v0.2.0 当前的 `maxPoolSize = 10` 与两台机器的实测结果一致,应继续保留。 + +## 被测版本 + +| 项目 | 值 | +| --- | --- | +| 仓库 | `AMDL-Web/wrapper-manager` | +| 版本 | v0.2.0 | +| 提交 | [`7dba4dc12359fddfa4a9f757ae7670402cc2166e`](https://github.com/AMDL-Web/wrapper-manager/commit/7dba4dc12359fddfa4a9f757ae7670402cc2166e) | +| wrapper 数量 | 2 个 Ready wrapper,区域均为 CN | +| 测试协议 | production gRPC fragment 双向流 | +| 测试日期 | z16:2026-07-16;rog:2026-07-17(Asia/Singapore) | + +测试 manager 使用基于上述提交构建的临时隔离镜像。它与生产版本的功能性差异只有一个:把编译期固定的 pool 10 暂时改成 `--pool-size 1..64`,用于逐档测试;生产源码和协议没有修改。 + +| 临时测试资产 | SHA-256 | +| --- | --- | +| manager isolation image | `025b61acb78bdf31f98eea383132040ca71dbf545b5e0abea66050d5699e8e5f` | +| media isolation image | `798424bd0f3bf298e61fe3ccfb017f5cd4e8cc9be8e052a24f1b9775db4f5ed0` | + +## 主机与 Docker 配置 + +| 项目 | z16 | rog | +| --- | --- | --- | +| 操作系统 | Windows 11 x64, build 10.0.26200 | Windows 11 x64, build 10.0.26200 | +| CPU | Intel Core i7-11800H | AMD Ryzen 9 9900X | +| 物理核心 / 逻辑线程 | 8C / 16T | 12C / 24T | +| 主机内存 | 16,841,486,336 bytes(约 15.68 GiB) | 33,426,591,744 bytes(约 31.13 GiB) | +| Docker Desktop | 28.5.1 | 29.6.1 | +| Docker 架构 | Linux / amd64 | Linux / amd64 | +| Docker 可用 CPU | 16 | 24 | +| Docker 内存上限 | 约 7.60 GiB | 约 15.19 GiB | +| Docker 存储驱动 | overlayfs | overlayfs | + +rog 的 Docker CPU 预算是 z16 的 1.5 倍,内存预算约为 2 倍;CPU 微架构与频率也明显更强。因此,两台机器的绝对吞吐不能只按核心数线性换算。 + +## 测试隔离方法 + +测试刻意把“下载媒体”和“manager + wrapper 解密”分离: + +- 真实 Apple Music 加密 ALAC 预先下载到 Docker volume; +- 计时客户端只连接 Docker `--internal` 网络,不能访问公网或 Apple Music CDN; +- manager 同时连接普通网络和 internal 网络,保留真实 license、key 和 context 条件; +- 客户端只能通过 manager 调用两个真实 wrapper; +- 下载、MP4 解析、SHA 校验、连接预热、remux、元数据、磁盘落盘和最终输出不计入计时窗口; +- 每个 pool 档位先完成一次 64 轨完整预热,再运行 5 个正式计时轮次; +- 每轮固定并发回放 64 个完整轨道,默认使用 1 个 gRPC `ClientConn`; +- 每 5 秒采样 manager 和客户端的 CPU、内存、网络与进程数,并周期性检查 decrypt 端口的实际连接数。 + +### 固定媒体 + +| 项目 | 值 | +| --- | --- | +| Adam ID | `1597687743` | +| 格式 | Apple Music 加密 ALAC, 24-bit / 96 kHz | +| 加密文件大小 | 152,492,529 bytes | +| 单轨逻辑明文大小 | 152,372,214 bytes(145.313 MiB) | +| 结构 | 9,466 samples;27 fragments;2 keys | +| 加密文件 SHA-256 | `88ddb2b23cf40ac9d2e05aa43b0f0fd39cc93d35ec84fba374d2394ef0802006` | +| 明文 SHA-256 | `e42fd1e7f09a18d92ab495c9dcf7c485947c26ca5541351bf41b3bb2af57daaf` | + +报告中的 MiB/s 是**逻辑明文吞吐**。实际链路同时承载请求侧密文、响应侧明文以及 protobuf / HTTP/2 开销,因此不能把该数字直接当作物理网卡吞吐。 + +## z16 实测结果 + +主矩阵固定 64 并发轨道,每档 1 次预热 + 5 次计时。 + +| Pool / wrapper | 两台总容量 | 吞吐中位 (MiB/s) | Wall 中位 (s) | P50 中位 (s) | P95 中位 (s) | manager 容器平均 CPU (cores) | manager 容器峰值内存 (MiB) | +| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| 4 | 8 | 72.081 | 129.022 | 77.040 | 127.124 | 8.01 | 830.56 | +| 6 | 12 | 84.758 | 109.724 | 61.684 | 108.051 | 9.99 | 830.11 | +| **8** | **16** | **94.320** | **98.602** | **64.920** | **97.756** | **10.71** | **832.10** | +| **10** | **20** | **94.645** | **98.262** | **67.106** | **97.001** | **10.59** | **833.96** | +| 12 | 24 | 94.671 | 98.236 | 69.220 | 97.429 | 11.14 | 837.46 | +| 16 | 32 | 95.203 | 97.686 | 75.719 | 97.075 | 11.46 | 842.68 | + +### z16 边际收益 + +| Pool 变化 | 吞吐中位变化 | 判断 | +| --- | ---: | --- | +| 4 → 6 | +17.6% | 仍在有效扩展区间 | +| 6 → 8 | +11.3% | 到达明显性能拐点 | +| 8 → 10 | +0.34% | 已进入平台 | +| 10 → 12 | +0.03% | 几乎没有收益 | +| 12 → 16 | +0.56% | 收益远低于资源增量 | + +z16 的理论性能拐点是 pool 8。生产使用 pool 10 可以在同一吞吐平台上为混合 Adam ID、license/context 波动和短时突发保留两路余量,因此比把默认值降到 8 更稳妥。 + +### z16 交叉验证 + +- 单流完整解密 3 轮的中位吞吐为 15.291 MiB/s。 +- pool 8 反向顺序复测 3 轮后,与原 5 轮合并的中位吞吐为 92.160 MiB/s,仍达到最佳值的 96.80%,说明拐点不是单纯由测试顺序造成。 +- pool 16 改用 4 个 gRPC `ClientConn` 后,3 轮中位吞吐为 89.915 MiB/s,比单 ClientConn 的 pool 16 低 5.55%。 + +## rog 实测结果 + +rog 使用相同镜像、固定媒体、网络隔离和主矩阵。 + +| Pool / wrapper | 两台总容量 | 吞吐中位 (MiB/s) | 单轮范围 (MiB/s) | P50 中位 (s) | P95 中位 (s) | 吞吐 CV | manager 容器平均 / 峰值 CPU (cores) | manager 容器峰值内存 (MiB) | +| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| 4 | 8 | 186.311 | 169.326–195.546 | 27.467 | 48.192 | 4.98% | 6.45 / 8.52 | 413.10 | +| 6 | 12 | 182.792 | 172.518–216.997 | 29.748 | 48.118 | 8.40% | 6.94 / 10.83 | 306.60 | +| 8 | 16 | 237.768 | 226.491–271.437 | 23.814 | 37.166 | 7.87% | 9.58 / 13.50 | 748.70 | +| **10** | **20** | **272.588** | **269.195–273.583** | **23.408** | **33.666** | **0.61%** | **10.68 / 13.97** | **823.60** | +| 12 | 24 | 275.147 | 269.762–275.224 | 23.996 | 33.375 | 0.78% | 10.77 / 14.53 | 767.30 | +| 16 | 32 | 189.126 | 162.449–270.222 | 28.387 | 44.603 | 19.22% | 7.37 / 15.51 | 385.10 | + +### rog 边际收益 + +| Pool 变化 | 吞吐中位变化 | 判断 | +| --- | ---: | --- | +| 4 → 6 | -1.89% | 波动区间,没有净收益 | +| 6 → 8 | +30.08% | 仍能有效扩展 | +| 8 → 10 | +14.64% | 达到稳定高吞吐区间 | +| 10 → 12 | +0.94% | 已进入平台 | +| 12 → 16 | -31.26% | 过度并发,明显劣化 | + +pool 10 已达到 pool 12 最佳中位吞吐的 99.07%,且五轮 CV 只有 0.61%。pool 12 的 0.94% 增益不足以覆盖额外连接、调度和混合负载风险,因此不适合作为新的全局默认值。 + +pool 16 虽然确实建立了 32 条 decrypt 连接,但中位吞吐下降到 189.126 MiB/s,CV 升至 19.22%。这证明问题不是“manager 没有真的开到目标并发”,而是更多流同时进入 wrapper 后放大了内部争用。 + +### rog 交叉验证 + +- 单流冒烟测试吞吐为 31.560 MiB/s,约为 z16 单流中位的 2.06 倍。 +- pool 16 / 4 ClientConn 的 3 轮吞吐为 157.215、169.107、162.077 MiB/s,中位 162.077 MiB/s;比单 ClientConn 的 pool 16 中位低 14.30%。 +- 主矩阵结束后反向重跑 pool 10,3 轮吞吐为 224.511、230.669、245.186 MiB/s,中位 230.669 MiB/s,仍显著高于同期 pool 16 / 4 ClientConn。 +- 反向 pool 10 未完全恢复到主矩阵的 272.588 MiB/s,说明长时间连续重负载下还存在热态、主机调度或外部 context 状态影响。因此主矩阵峰值不应被视为任何时刻都可保证的生产吞吐;但它不改变 pool 10–12 进入平台、pool 16 过度并发的判断。 + +未继续测试 pool 20、24、32。预先设定的扩展条件是“上一档到下一档仍有至少 5% 的稳定中位吞吐增益,并且 P95、错误率和资源仍可接受”;rog 的 pool 12 → 16 实际为 -31.26%,已经明确不满足继续扩展的条件。 + +## 两台机器对比 + +| 指标 | z16 | rog | rog / z16 | +| --- | ---: | ---: | ---: | +| 单流吞吐 | 15.291 MiB/s | 31.560 MiB/s | 2.06× | +| 最佳主矩阵中位吞吐 | 95.203 MiB/s(pool 16) | 275.147 MiB/s(pool 12) | 2.89× | +| pool 10 中位吞吐 | 94.645 MiB/s | 272.588 MiB/s | 2.88× | +| 推荐生产 pool / wrapper | 10 | 10 | 相同 | +| 推荐两台 wrapper 总活动流 | 20 | 20 | 相同 | + +更强的 CPU 显著提高绝对吞吐,但没有证明应该无限提高单 wrapper 并发。rog 在 pool 10 已充分利用更多 CPU;继续增加到 12 只有边际收益,提高到 16 则破坏稳定性。 + +## 为什么并发不会线性扩展 + +测试排除了以下替代解释: + +- **连接池未生效:** 各档 decrypt 连接数均精确达到目标,两台 wrapper 分配对称; +- **manager 全局锁或单核饱和:** manager Go 进程本身没有表现为固定单核上限,主要 CPU 消耗来自两个 wrapper 子进程; +- **单 gRPC ClientConn 限流:** 改成 4 个 ClientConn 后吞吐反而下降; +- **客户端发生器 CPU 不足:** 客户端 CPU 明显低于 manager + wrapper 总消耗,且更换 ClientConn 没有改善; +- **下载、CDN 或磁盘瓶颈:** 计时客户端位于 internal 网络,固定加密媒体已经预载入内存流程。 + +当 pool 超过甜点值后,限制因素转移到 wrapper 内部处理能力:解密、codec/样本处理、进程间通信、内存复制、调度和共享资源争用。manager 可以把更多流送进 wrapper,但不能按连接数比例放大单机计算能力。 + +## 生产建议 + +1. 保持 `maxPoolSize = 10`,不要把全局默认值提高到 12 或 16。 +2. 两个 wrapper 的常规生产总活动流保持 20;后端可以有更多排队任务,但不应把所有任务同时挤入 wrapper。 +3. 如果需要更高总吞吐,优先增加独立 wrapper 主机或实例,并重新测试负载分配,而不是继续提高单 wrapper pool。 +4. 只有在以下条件发生变化后才需要重测:wrapper 内部解密/IPC/codec 实现改变、wrapper 数量改变、CPU 拓扑或 Docker CPU 配额改变、媒体格式分布显著改变。 +5. 重测时继续使用固定媒体、internal 客户端网络、至少 5 个正式轮次、明文 SHA-256 校验以及实际 TCP 连接数验证。 + +## 完整性结果 + +- 主矩阵与交叉验证的所有明文 SHA-256 均一致; +- 0 gRPC error、0 timeout、0 EOF、0 broken pipe; +- 0 manager 重启、0 wrapper 意外退出; +- rog 共完成 36 个正式计时轮次及 8 次完整预热,处理约 399.6 GiB 逻辑明文; +- 测试结束后,rog 上的凭据副本、临时 volume、容器和网络已删除,z16 生产 manager 已恢复并验证两个 wrapper Ready。