You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C# UDP套接字中减少内存分配与GC波动?

解决C# UDP接收时Socket.ReceiveFrom的额外内存分配问题

问题根源

你遇到的内存分配主要来自两个核心点:

  • EndPoint实例重复分配:如果每次调用ReceiveFrom前都重新创建remoteEndPoint,或者初始化为null,底层会在调用时自动分配新的EndPoint对象(调试里看到的656字节分配就源于此)。
  • 传统数组重载的内部堆分配:旧版ReceiveFrom(byte[], ...)重载在底层处理逻辑中,会产生临时堆内存分配(比如调试里的204字节缓冲区分配)。

无额外分配的解决方案

1. 预先复用EndPoint实例

提前初始化一个IPEndPoint实例并全程复用,不要每次接收都创建新对象:

// 预先初始化,在整个接收生命周期内复用
var remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
byte[] buffer = new byte[1024];

// 接收循环
while (true)
{
    int byteCount = socket.ReceiveFrom(buffer, SocketFlags.None, ref remoteEndPoint);
    // 处理接收到的数据...
    // 无需重新创建remoteEndPoint,下次调用会自动更新其IP和端口信息
}

此操作可完全消除EndPoint相关的重复内存分配。

2. 使用Span重载的ReceiveFrom(.NET Core 2.1+/.NET 5+)

Span是值类型,不会在堆上分配内存,对应的ReceiveFrom(Span<byte>, ...)重载能彻底避免底层的临时缓冲区分配:

var remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
byte[] buffer = new byte[1024];
Span<byte> bufferSpan = buffer.AsSpan();

while (true)
{
    int byteCount = socket.ReceiveFrom(bufferSpan, SocketFlags.None, ref remoteEndPoint);
    // 处理bufferSpan[0..byteCount]范围内的数据...
}

这个重载直接操作你预先分配的数组的Span,不会额外分配内存,完全消除调试中看到的204字节缓冲区分配。

3. 可选:用ArrayPool复用缓冲区(高并发场景)

如果需要动态调整缓冲区大小或在多线程场景下复用资源,可结合ArrayPool<byte>进一步优化:

var remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
var bufferPool = ArrayPool<byte>.Shared;
byte[] buffer = bufferPool.Rent(1024);

try
{
    Span<byte> bufferSpan = buffer.AsSpan();
    while (true)
    {
        int byteCount = socket.ReceiveFrom(bufferSpan, SocketFlags.None, ref remoteEndPoint);
        // 处理数据...
    }
}
finally
{
    bufferPool.Return(buffer);
}

这能避免频繁创建销毁大数组带来的GC压力,若使用固定大小缓冲区,直接预先分配数组即可。

验证效果

采用上述方案后,调试时将不会再看到ReceiveFrom产生的额外堆分配,所有内存均来自你预先分配并复用的资源,可彻底消除GC波动。

内容的提问来源于stack exchange,提问作者Dogtanian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 03:22:35