Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 187 additions & 0 deletions CONCURRENCY_BENCHMARK.md
Original file line number Diff line number Diff line change
@@ -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。
Loading