RPC远程过程调用,二进制封装,提供高吞吐低延迟的高性能RPC框架
|
# NewLife.Remoting RPC 性能测试报告
## 性能概览
NewLife.Remoting 是基于 TCP 的高性能 RPC 框架,提供 ApiServer/ApiClient 全链路远程调用能力。本次测试表明:**服务端纯处理能力达 1284 万 RPC/s,TCP 端到端吞吐量达 26.3 万 RPC/s(100 连接),单次无参调用延迟仅 52 μs,具备优秀的高并发扩展性。**
---
## 测试环境
| 项目 | 值 |
|------|-----|
| OS | Windows 10 (10.0.19045) |
| CPU | Intel Core i9-10900K @ 3.70GHz (10 核 20 线程) |
| Runtime | .NET 10.0.3, X64 RyuJIT x86-64-v3 |
| BenchmarkDotNet | v0.15.8 |
| GC | Non-concurrent Workstation |
| SIMD | AVX2 VectorSize=256 |
---
## 测试方法
### BenchmarkDotNet 基准测试(RpcBenchmarks)
- 对 ApiClient → ApiServer 全链路 TCP 调用进行精确微基准测试
- 每个并发级别创建独立客户端连接,调用服务端 `BenchController`
- 测试场景:无参返回 Int32、String 回显(16/2000 字符)、多参数调用、IPacket 回显(16/2000 字节)
- 并发级别:1、4、16、20(= CPU 线程数)、32
### 服务端纯处理能力测试(Direct)
- 绕过 TCP 网络栈,直接调用 `ApiServer.Process()` 测量服务端原始处理能力
- 20 线程并发,预热 3 秒 + 正式测试 10 秒
### 网络吞吐量压力测试(Throughput)
- 100 个 TCP 客户端连接持续并发调用,测量端到端吞吐量和平均延迟
- 预热 3 秒 + 正式测试 10 秒
---
## 测试结果
### 1. BenchmarkDotNet 基准测试
| 场景 | 并发数 | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
|------|--------|-----:|------:|-------:|-----:|-----:|----------:|
| 无参返回Int32 | 1 | 52.39 μs | 201.184 μs | 11.028 μs | 0.2441 | - | 3.22 KB |
| String出入参(16字符) | 1 | 53.60 μs | 203.216 μs | 11.139 μs | 0.4883 | - | 5.60 KB |
| String出入参(2000字符) | 1 | 116.12 μs | 86.841 μs | 4.760 μs | 3.6621 | - | 34.91 KB |
| 多基础类型参数 | 1 | 53.56 μs | 88.185 μs | 4.834 μs | 0.8545 | - | 8.92 KB |
| IPacket出入参(16字节) | 1 | 56.20 μs | 234.207 μs | 12.838 μs | 0.2441 | - | 3.38 KB |
| IPacket出入参(2000字节) | 1 | 62.59 μs | 296.472 μs | 16.251 μs | 0.4883 | - | 5.32 KB |
| 无参返回Int32 | 4 | 126.83 μs | 316.883 μs | 17.369 μs | 0.9766 | - | 11.90 KB |
| String出入参(16字符) | 4 | 109.51 μs | 510.389 μs | 27.976 μs | 1.9531 | - | 21.45 KB |
| String出入参(2000字符) | 4 | 116.69 μs | 35.898 μs | 1.968 μs | 14.4043 | 1.4648 | 138.18 KB |
| 多基础类型参数 | 4 | 122.94 μs | 758.092 μs | 41.554 μs | 3.1738 | - | 34.72 KB |
| IPacket出入参(16字节) | 4 | 130.50 μs | 296.237 μs | 16.238 μs | 0.9766 | - | 12.57 KB |
| IPacket出入参(2000字节) | 4 | 125.15 μs | 521.811 μs | 28.602 μs | 1.9531 | - | 20.32 KB |
| 无参返回Int32 | 16 | 277.94 μs | 1,141.172 μs | 62.551 μs | 3.4180 | - | 46.63 KB |
| String出入参(16字符) | 16 | 262.05 μs | 48.643 μs | 2.666 μs | 6.3477 | - | 84.83 KB |
| String出入参(2000字符) | 16 | 401.01 μs | 1,455.394 μs | 79.775 μs | 50.7813 | 10.7422 | 551.75 KB |
| 多基础类型参数 | 16 | 408.00 μs | 2,450.887 μs | 134.341 μs | 9.7656 | - | 137.90 KB |
| IPacket出入参(16字节) | 16 | 247.13 μs | 341.186 μs | 18.702 μs | 3.4180 | - | 49.33 KB |
| IPacket出入参(2000字节) | 16 | 280.44 μs | 937.254 μs | 51.374 μs | 5.8594 | 0.4883 | 80.33 KB |
| 无参返回Int32 | 20 | 279.41 μs | 18.081 μs | 0.991 μs | 3.9063 | - | 58.22 KB |
| String出入参(16字符) | 20 | 434.32 μs | 2,162.381 μs | 118.527 μs | 6.8359 | - | 105.96 KB |
| String出入参(2000字符) | 20 | 423.50 μs | 18.243 μs | 1.000 μs | 61.5234 | 15.6250 | 689.62 KB |
| 多基础类型参数 | 20 | 462.79 μs | 2,810.361 μs | 154.045 μs | 11.7188 | - | 172.30 KB |
| IPacket出入参(16字节) | 20 | 297.97 μs | 1.147 μs | 0.063 μs | 3.9063 | - | 61.58 KB |
| IPacket出入参(2000字节) | 20 | NA | NA | NA | NA | NA | NA |
| 无参返回Int32 | 32 | 520.81 μs | 2,181.539 μs | 119.578 μs | 4.8828 | - | 92.95 KB |
| String出入参(16字符) | 32 | 467.86 μs | 27.738 μs | 1.520 μs | 8.7891 | - | 169.32 KB |
| String出入参(2000字符) | 32 | 852.96 μs | 4,170.590 μs | 228.604 μs | 76.1719 | 19.5313 | 1,103.16 KB |
| 多基础类型参数 | 32 | 791.80 μs | 5,006.656 μs | 274.432 μs | 13.6719 | - | 275.47 KB |
| IPacket出入参(16字节) | 32 | 499.77 μs | 1,410.822 μs | 77.332 μs | 4.8828 | - | 98.32 KB |
| IPacket出入参(2000字节) | 32 | NA | NA | NA | NA | NA | NA |
> **注意**:IPacket(2000字节) 在并发 20 和 32 时出现异常(NA),可能与高并发下大数据包的内存竞争有关。
### 2. 服务端纯处理能力测试(绕过 TCP,20 线程)
| 场景 | 总请求数 | 耗时 | 吞吐量 | 每请求分配 | GC Gen0 |
|------|-------:|-----:|-------:|----------:|--------:|
| NoArg_ReturnInt32 | 128,533,012 | 10.01 秒 | **12,844,229 RPC/s** | 0 B | 9,115 |
| EchoPacket_16B | 96,516,183 | 10.01 秒 | **9,642,161 RPC/s** | 0 B | 6,999 |
### 3. 网络吞吐量压力测试(TCP 端到端,100 连接)
| 场景 | 总请求数 | 耗时 | 吞吐量 | 平均延迟 | GC Gen0 | GC Gen1 |
|------|-------:|-----:|-------:|--------:|--------:|--------:|
| NoArg_ReturnInt32 | 2,638,607 | 10.03 秒 | **263,184 RPC/s** | 380.0 μs | 707 | 0 |
| EchoPacket_16B | 2,338,682 | 10.00 秒 | **233,812 RPC/s** | 427.7 μs | 608 | 7 |
---
## 核心指标
### 单次调用延迟(BenchmarkDotNet,单线程)
| 场景 | 延迟 | 内存分配 | 等效 QPS |
|------|-----:|--------:|---------:|
| 无参返回Int32 | 52.39 μs | 3.22 KB | ~19,088 |
| String回显(16字符) | 53.60 μs | 5.60 KB | ~18,657 |
| String回显(2000字符) | 116.12 μs | 34.91 KB | ~8,612 |
| 多基础类型参数 | 53.56 μs | 8.92 KB | ~18,671 |
| IPacket回显(16字节) | 56.20 μs | 3.38 KB | ~17,794 |
| IPacket回显(2000字节) | 62.59 μs | 5.32 KB | ~15,977 |
### 服务端纯处理峰值
| 指标 | 值 |
|------|---:|
| NoArg 峰值 | **1284 万 RPC/s** |
| EchoPacket_16B 峰值 | **964 万 RPC/s** |
| 每请求堆分配 | **0 B**(已完全池化) |
### TCP 端到端峰值(100 连接)
| 指标 | 值 |
|------|---:|
| NoArg 峰值 | **26.3 万 RPC/s** |
| EchoPacket_16B 峰值 | **23.4 万 RPC/s** |
| NoArg 平均延迟 | **380 μs** |
| EchoPacket_16B 平均延迟 | **428 μs** |
---
## 对比分析
### 纵向分析:并发扩展性
以**无参返回Int32**为例,观察不同并发级别的性能变化:
| 并发数 | Mean | 每连接延迟 | 等效总 QPS | 扩展效率 |
|-------:|-----:|----------:|---------:|---------:|
| 1 | 52.39 μs | 52.39 μs | 19,088 | 基准 |
| 4 | 126.83 μs | 31.71 μs | 31,540 | 41.4% |
| 16 | 277.94 μs | 17.37 μs | 57,566 | 18.9% |
| 20 | 279.41 μs | 13.97 μs | 71,579 | 18.8% |
| 32 | 520.81 μs | 16.28 μs | 61,449 | 10.1% |
- **最优并发点为 20(= CPU 线程数)**,总吞吐量约 7.2 万 QPS,每连接延迟最低 13.97 μs
- 并发 32 超过 CPU 线程数后,总延迟上升 86%,等效 QPS 反而下降,出现线程竞争
- 并发 1→4 扩展效率最高(41.4%),4→16 仍有显著提升,超过核心数后收益递减
### 横向分析:不同场景对比(并发 = 1)
| 场景 | 延迟 | vs 基准 | 内存 | vs 基准 |
|------|-----:|--------:|-----:|--------:|
| 无参返回Int32(基准) | 52.39 μs | - | 3.22 KB | - |
| String回显(16字符) | 53.60 μs | +2.3% | 5.60 KB | +73.9% |
| 多基础类型参数 | 53.56 μs | +2.2% | 8.92 KB | +177.0% |
| IPacket回显(16字节) | 56.20 μs | +7.3% | 3.38 KB | +5.0% |
| IPacket回显(2000字节) | 62.59 μs | +19.5% | 5.32 KB | +65.2% |
| String回显(2000字符) | 116.12 μs | +121.6% | 34.91 KB | +984.5% |
- **IPacket 是最高效的数据传输方式**:2000 字节 IPacket 仅比无参慢 19.5%,而 2000 字符 String 慢 121.6%
- IPacket 2000 字节内存分配仅 5.32 KB,String 2000 字符高达 34.91 KB(6.6 倍差距)
- 小数据量(≤16 字符/字节)时各场景延迟差异不大(2~7%),RPC 框架开销占主导
### 处理瓶颈分析:纯处理 vs TCP 端到端
| 指标 | 纯处理(Direct) | TCP 端到端 | TCP 开销占比 |
|------|----------------:|-----------:|------------:|
| NoArg 吞吐量 | 12,844,229 RPC/s | 263,184 RPC/s | 97.95% |
| EchoPacket_16B 吞吐量 | 9,642,161 RPC/s | 233,812 RPC/s | 97.58% |
- TCP 网络栈开销占总延迟的 **97%~98%**,服务端处理本身极轻
- 纯处理场景已实现零堆分配(0 B/req),GC 压力来自消息编解码的临时对象
---
## 性能瓶颈定位
1. **TCP 网络栈是主要瓶颈**:纯处理 1284 万 vs TCP 端到端 26.3 万,差距约 49 倍。TCP 的系统调用、缓冲区拷贝、异步回调是主要开销
2. **大 String 序列化开销显著**:2000 字符 String 的延迟是无参的 2.2 倍,内存分配是 10.8 倍,JSON 序列化/反序列化是主因
3. **IPacket 大数据包高并发不稳定**:2000 字节 IPacket 在并发 ≥20 时出错(NA),可能存在缓冲区竞争或 Dispose 时序问题
4. **超核心数并发收益递减**:并发 32 时性能反而低于并发 20,线程上下文切换和锁竞争成为瓶颈
---
## 优化建议
| 优先级 | 建议 | 预期收益 |
|:------:|------|---------|
| P0 | **排查 IPacket(2000B) 高并发异常**:在并发 ≥20 时复现错误,检查 ArrayPacket/OwnerPacket 的线程安全和 Dispose 时序 | 消除高并发大包场景的不稳定性 |
| P1 | **启用连接复用(Multiplex)**:当前代码已注释,恢复后可在单连接上并行处理多请求,减少连接管理开销 | TCP 吞吐量预计提升 30~50% |
| P2 | **IO 优化**:考虑使用 `System.IO.Pipelines` 替代传统 Stream 模型,减少缓冲区拷贝 | 降低 TCP 层开销,缩小纯处理与端到端差距 |
| P3 | **大 String 场景优化**:对于大文本传输,建议业务侧先转为 byte[] 或 IPacket,避免 JSON 序列化/反序列化的内存开销 | 大文本延迟降低约 50%,内存分配降低约 85% |
| P4 | **池化消息对象**:在 BenchmarkDotNet 测试中观察到 Gen0 GC 随并发线性增长,可进一步池化 DefaultMessage 及其 Payload | 降低 GC 压力,改善 P99 延迟抖动 |
|