NewLife/X

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
7de8769
Tree
1 Parent(s) a4f0546
Summary: 2 changed files with 62 additions and 30 deletions.
Modified +40 -12
Modified +22 -18
Modified +40 -12
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)
Modified +22 -18
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 万包/秒` 目标,后续应重点优化任务调度、批量发送与回调路径,并继续搜索最佳并发窗口。