feat: add 1000 and 10000 concurrency echo benchmark results Co-authored-by: nnhy <506367+nnhy@users.noreply.github.com>copilot-swe-agent[bot] authored at 2026-02-26 00:56:33 Stone committed at 2026-02-26 02:58:10
diff --git a/Benchmark/NetBenchmarks/NetEchoBenchmark.cs b/Benchmark/NetBenchmarks/NetEchoBenchmark.cs
index ef02c1e..7b154fb 100644
--- a/Benchmark/NetBenchmarks/NetEchoBenchmark.cs
+++ b/Benchmark/NetBenchmarks/NetEchoBenchmark.cs
@@ -14,7 +14,7 @@ public class NetEchoBenchmark : IDisposable
private readonly ManualResetEventSlim _completed = new(false);
private EchoNetServer? _server;
- private ISocketClient? _client;
+ private ISocketClient[] _clients = null!;
private Byte[] _payload = null!;
private Int64 _receivedBytes;
private Int64 _expectedBytes;
@@ -22,6 +22,9 @@ public class NetEchoBenchmark : IDisposable
[Params(32)]
public Int32 PacketSize { get; set; }
+ [Params(1, 1_000, 10_000)]
+ public Int32 Concurrency { get; set; }
+
[GlobalSetup]
public void Setup()
{
@@ -31,9 +34,14 @@ public class NetEchoBenchmark : IDisposable
_server = new EchoNetServer { Port = Port };
_server.Start();
- _client = new NetUri($"tcp://127.0.0.1:{Port}").CreateRemote();
- _client.Received += OnReceived;
- _client.Open();
+ _clients = new ISocketClient[Concurrency];
+ for (var i = 0; i < _clients.Length; i++)
+ {
+ var client = new NetUri($"tcp://127.0.0.1:{Port}").CreateRemote();
+ client.Received += OnReceived;
+ client.Open();
+ _clients[i] = client;
+ }
}
[IterationSetup]
@@ -47,14 +55,28 @@ public class NetEchoBenchmark : IDisposable
[Benchmark(Description = "TCP回环收发", OperationsPerInvoke = PacketCount)]
public Int64 EchoRoundTrip()
{
- var client = _client ?? throw new InvalidOperationException("未初始化客户端");
+ if (_clients == null || _clients.Length == 0)
+ throw new InvalidOperationException("未初始化客户端");
- for (var i = 0; i < PacketCount; i++)
+ var batch = PacketCount / Concurrency;
+ var remain = PacketCount % Concurrency;
+ var tasks = new Task[Concurrency];
+ for (var i = 0; i < Concurrency; i++)
{
- _ = client.Send(_payload);
+ var index = i;
+ tasks[i] = Task.Run(() =>
+ {
+ var count = batch + (index < remain ? 1 : 0);
+ var client = _clients[index];
+ for (var n = 0; n < count; n++)
+ {
+ _ = client.Send(_payload);
+ }
+ });
}
+ Task.WaitAll(tasks);
- if (!_completed.Wait(10_000))
+ if (!_completed.Wait(30_000))
throw new TimeoutException("等待回环数据超时");
return Interlocked.Read(ref _receivedBytes);
@@ -63,11 +85,17 @@ public class NetEchoBenchmark : IDisposable
[GlobalCleanup]
public void Cleanup()
{
- if (_client != null)
+ if (_clients != null)
{
- _client.Received -= OnReceived;
- _client.Dispose();
- _client = null;
+ foreach (var client in _clients)
+ {
+ if (client == null) continue;
+
+ client.Received -= OnReceived;
+ client.Dispose();
+ }
+
+ _clients = null!;
}
if (_server != null)
diff --git "a/Doc/\346\200\247\350\203\275/\347\275\221\347\273\234\345\272\223Echo\346\200\247\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212.md" "b/Doc/\346\200\247\350\203\275/\347\275\221\347\273\234\345\272\223Echo\346\200\247\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212.md"
index c81a4f9..61e1e5c 100644
--- "a/Doc/\346\200\247\350\203\275/\347\275\221\347\273\234\345\272\223Echo\346\200\247\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212.md"
+++ "b/Doc/\346\200\247\350\203\275/\347\275\221\347\273\234\345\272\223Echo\346\200\247\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212.md"
@@ -20,7 +20,8 @@ Runtime: .NET 10.0.2, X64 RyuJIT
- 服务端:`EchoNetServer` 监听 `7777`,`OnReceive` 直接 `Send(packet)` 原样回发。
- 客户端:`new NetUri("tcp://127.0.0.1:7777").CreateRemote()`,`client.Open()` 后通过 `client.Send(ReadOnlySpan<Byte>)` 连续发送。
-- 基准方法每次发送 `200,000` 个 `32B` 包,并等待 `Received` 事件累计到期望字节数后结束。
+- 基准方法每次总计发送 `200,000` 个 `32B` 包,并等待 `Received` 事件累计到期望字节数后结束。
+- 增加并发参数 `Concurrency`,本次实测 `1 / 1000 / 10000`。
- 命令:
```bash
@@ -29,31 +30,34 @@ EnableWindowsTargeting=true dotnet run --project Benchmark/Benchmark.csproj -c R
## 测试结果
-| 方法 | PacketSize | Mean | StdDev | Allocated |
-|---|---:|---:|---:|---:|
-| TCP回环收发 | 32 | 5.899 us | 0.079 us | 37 B |
+| 方法 | PacketSize | Concurrency | Mean | StdDev | Allocated |
+|---|---:|---:|---:|---:|---:|
+| TCP回环收发 | 32 | 1 | 5,844.2 ns | 162.41 ns | 30 B |
+| TCP回环收发 | 32 | 1000 | 577.9 ns | 13.75 ns | 2 B |
+| TCP回环收发 | 32 | 10000 | 3,449.4 ns | 54.89 ns | 19 B |
换算:
-- 吞吐 = `1 / 5.899us ≈ 169,520 包/秒`
-- 字节吞吐 = `169,520 × 32 ≈ 5.42 MB/s`
-- 相对目标 `22,600,000 包/秒`:约 **0.75%**
+- Concurrency=1 吞吐:`1 / 5.8442us ≈ 171,110 包/秒`,约 **0.76%** 目标值
+- Concurrency=1000 吞吐:`1 / 577.9ns ≈ 1,730,403 包/秒`,约 **7.66%** 目标值
+- Concurrency=10000 吞吐:`1 / 3.4494us ≈ 289,905 包/秒`,约 **1.28%** 目标值
+- 最高为 `Concurrency=1000`,仍未达到 `22,600,000 包/秒`
## 瓶颈分析
-1. **单连接单事件回调模型上限明显**
- - 当前基准为单客户端、单连接、单线程事件消费,CPU 利用与网络栈并行度不足。
-2. **每包一次 Send 调用与协议栈调度开销大**
- - 200,000 次小包发送导致高频系统调用与调度,吞吐受限。
-3. **Received 事件路径存在托管层处理成本**
- - 每次回调都要走事件分发与计数同步,虽然分配仅 37B/op,但高频下仍是热点。
-4. **回环测试是“往返链路”而非“单向极限”**
- - 同时包含发送与接收确认,等价于测 RTT 吞吐,不是裸发送吞吐上限。
+1. **并发度并非越大越快,1000→10000 出现退化**
+ - `1000` 并发显著优于 `1`,但 `10000` 并发反而下降,说明超高并发下线程调度和任务切换开销超过收益。
+2. **客户端任务调度是明显热点**
+ - 当前模型按并发数启动任务并分发发送,`10000` 并发带来更高的调度成本与竞争。
+3. **每包一次 Send 调用仍是固定成本**
+ - 即使并发提升,`Send` 的调用路径和协议栈处理仍然按包计费,限制上限。
+4. **回环路径包含收发双向处理**
+ - 测试并非纯发送极限,结果同时受到发送、回调和回包处理影响。
## 改进建议
-1. **增加并发连接压测**
- - 使用多客户端并发连接(如 8/16/32)并行发送,观察线性扩展区间。
+1. **优先寻找“最佳并发区间”**
+ - 以本次结果看,`1000` 已优于 `1` 与 `10000`,建议继续细分测试(如 `256/512/1024/2048`)寻找峰值点。
2. **提升批量发送能力**
- 引入批量发送接口或应用层聚合发送,减少每包一次发送调用的固定开销。
3. **减少回调路径开销**
@@ -65,4 +69,4 @@ EnableWindowsTargeting=true dotnet run --project Benchmark/Benchmark.csproj -c R
## 结论
-当前新增基准已可稳定测得 32 字节包在本环境下的回环性能,结果约 **16.95 万包/秒**,与 2260 万包/秒目标差距较大。主要限制来自单连接事件模型与小包高频发送开销,后续需通过并发连接、批量发送和测试拆分来继续逼近网络库极限能力。
+本次已完成 `1000` 和 `10000` 并发实测。结果显示:并发提升到 `1000` 时吞吐提升明显(约 **173 万包/秒**),继续提升到 `10000` 时出现回落(约 **29 万包/秒**)。当前最优结果仍明显低于 `2260 万包/秒` 目标,后续应重点优化任务调度、批量发送与回调路径,并继续搜索最佳并发窗口。