c# System.IO.Pipelines 和 NetworkStream 的性能对比

10次阅读

Pipelines 比.networkstream 快的根本原因是零拷贝内存访问和无状态机同步模型,减少 GC 压力 30%~60%、CPU 时间 20%~40%,尤其适合小包高频复用场景。

c# System.IO.Pipelines 和 NetworkStream 的性能对比

System.IO.Pipelines 为什么比 NetworkStream 快

根本原因不在“管道”本身,而在内存管理和同步模型。NetworkStream 默认走 Stream.ReadAsync / WriteAsync,每次调用都触发一次 MemoryArraySegment → 内部缓冲区拷贝,还常伴随 Task 分配和状态机开销。Pipelines 的 PipeReader 直接暴露 ReadOnlySequence,数据零拷贝进入用户逻辑;PipeWriterAdvance 也只移动指针,不搬运字节

典型场景下(如 http 解析、Protobuf 反序列化),Pipelines 能减少 30%~60% 的 GC 压力和 20%~40% 的 CPU 时间 —— 尤其在小包高频通信时更明显。

NetworkStream 什么时候反而更简单可靠

不是所有场景都值得为性能上 Pipelines。如果你只是做一次性 TCP 连接、协议简单(比如发个 jsON 请求拿个响应)、吞吐量低于 1K QPS,NetworkStream 的代码量和调试成本显著更低。

  • NetworkStream 天然支持 Timeout 属性(ReadTimeout/WriteTimeout),而 Pipelines 需手动结合 CancellationToken + ValueTask 状态判断
  • 异常更直白:IOException: Unable to read data from the transport connection 比 Pipelines 中 InvalidOperationException: Cannot await a completed result 更容易定位网络中断
  • HttpClientTcpClient.GetStream() 无缝衔接,无额外适配层

真实压测中 Pipelines 的关键配置陷阱

没调好 PipeOptions,Pipelines 可能比 NetworkStream 还慢。常见误配:

  • 默认 PipeOptions.PoolSize = 4096,但高并发下小 buffer 频繁分配会触发 GC —— 建议设为 8192 或更高(需权衡内存占用
  • MinimumSegmentSize 设太小(如 512)会导致大量小段内存碎片;设太大(如 1MB)又浪费;推荐 4KB~16KB 区间,匹配多数网卡 MTU
  • 漏掉 UseSynchronizationContext = false,在 ASP.NET Core 默认同步上下文里会引发线程争用

正确初始化示例:

var options = new PipeOptions(     pool: ArrayPool.Create(8192, 1024),     minimumSegmentSize: 4096,     useSynchronizationContext: false); var pipe = new Pipe(options);

从 NetworkStream 迁移到 Pipelines 的最小改动路径

不要重写整个通信层。优先替换接收侧,保留 NetworkStream 发送逻辑过渡:

  • TcpClient.GetStream() 获取 NetworkStream 后,立即包装成 StreamPipeReadernew StreamPipeReader(stream, options)
  • 发送仍用 stream.WriteAsync(...),等读侧稳定后再把写逻辑迁到 PipeWriter
  • 注意:Pipelines 不自动处理粘包/半包,必须自己实现 SequenceReader 边界判断 —— 别直接用 reader.TryRead(out var result) 就解析,要循环直到满足协议长度

粘包处理示意:

while (true) {     var result = await reader.ReadAsync(ct);     var buffer = result.Buffer;     if (!buffer.IsEmpty)     {         var reader = new SequenceReader(buffer);         while (reader.TryReadLittleEndian(out int len) && reader.Remaining >= len)         {             // 解析 len 字节的有效载荷             reader.Advance(len);         }         reader.AdvanceTo(buffer.Start, buffer.End);     }     if (result.IsCompleted) break; }

Pipelines 的性能优势只有在协议解析逻辑足够轻、且连接复用率高时才真正释放。单次短连接 + 复杂 json 序列化,NetworkStream 可能更省心。

text=ZqhQzanResources