C# 高性能内存优化:全面理解 Span 与 stackalloc
在 .NET / Unity 开发中,GC(垃圾回收)常常是性能瓶颈。
从 C# 7.2 开始,微软引入了 Span<T> 和 stackalloc,为开发者提供了 零 GC、高性能的内存操作工具。
本文将系统总结 Span 与 stackalloc 的概念、规则、注意事项与常见应用。
1. Span 是什么?
Span<T> 是一个 轻量级切片结构(ref struct),用于操作连续内存。
它本身不分配堆内存,只存储 指针 + 长度,因此避免了 GC 压力。
1.1 特点
- ✅ 零 GC:不创建新对象
- ✅ 高效切片:无需复制数组即可获取子范围
- ✅ 安全:不像指针,依旧受类型系统保护
- ⚠️ 作用域受限:
Span<T>是ref struct,不能存到普通 class 的字段里
1.2 示例
int[] arr = {1,2,3,4,5};
Span<int> slice = arr.AsSpan(1,3); // [2,3,4]
slice[0] = 99;
Console.WriteLine(arr[1]); // 输出 99
2. Span 的字段限制
由于 Span<T> 是 ref struct,它有严格的使用规则:
- ❌ 不能装箱(boxing)
- ❌ 不能作为 普通 class 的字段
- ✅ 可以作为 ref struct 的字段
- ✅ 可以作为方法局部变量、参数、返回值
ref struct MyBuffer
{
public Span<int> Data; // ✅ 合法
}
class Foo
{
public Span<int> Data; // ❌ 编译报错
}
👉 原因:普通类在堆上存活时间更长,可能导致 Span 引用的栈内存已失效,产生悬空引用。
3. stackalloc:栈上分配内存
stackalloc 用于在 栈上分配一块连续内存。
它的生命周期与方法相同,方法返回时自动销毁。
3.1 示例
Span<int> buffer = stackalloc int[100]; // 栈上分配
for (int i = 0; i < buffer.Length; i++)
buffer[i] = i * 2;
Console.WriteLine(buffer[10]); // 输出 20
3.2 特点
- ✅ 极快:栈分配开销极低
- ✅ 零 GC:不进入托管堆
- ⚠️ 大小受限:栈空间有限(不能分配太大)
- ⚠️ 生命周期短:随方法退出自动销毁
4. stackalloc ≠ 必须配合 Span
stackalloc 最早在 C# 1.0 就存在,最初用于 指针:
unsafe
{
int* arr = stackalloc int[100];
arr[0] = 42;
}
从 C# 7.2 开始,可以直接与 Span<T> 配合使用:
Span<int> arr = stackalloc int[100];
👉 推荐现代写法:stackalloc + Span<T>,安全高效。
5. stackalloc 分配的内存未初始化
⚠️ 这是一个容易忽视的坑点:
new int[100]→ 自动初始化为0stackalloc int[100]→ 未初始化,里面可能是栈上的垃圾数据
示例:
Span<int> buffer = stackalloc int[5];
for (int i = 0; i < buffer.Length; i++)
Console.WriteLine(buffer[i]); // 输出随机垃圾值
// 如果需要初始化为0
buffer.Clear();
6. Span 作为方法参数
在高性能 API 中,Span<T> 常作为方法参数出现,典型模式是 TryParse / TryFormat:
6.1 TryFormat 示例
Span<char> buffer = stackalloc char[16];
if (12345.TryFormat(buffer, out int written))
{
Console.WriteLine(buffer.Slice(0, written).ToString()); // "12345"
}
6.2 规律
- 输入用
ReadOnlySpan<T>→ 只读数据(如 TryParse) - 输出用
Span<T>→ 写入结果(如 TryFormat) - 返回 bool → 表示是否成功,避免异常带来性能开销
👉 这种模式广泛应用于 .NET Core 的数值、字符串、GUID、日期等 API。
7. 常见使用场景
在 .NET / Unity / 游戏开发中,Span 与 stackalloc 特别适合:
- 🔹 每帧临时字符串格式化(避免 GC)
- 🔹 音频采样缓冲区
- 🔹 顶点 / 网格数据处理
- 🔹 临时算法工作区(排序、解析等)
8. 总结
- Span:轻量切片,零 GC,但作用域有限
- stackalloc:栈分配,快速无 GC,但未初始化、生命周期短
- 组合使用:适合高频临时数据处理,显著降低 GC 压力
- API 模式:TryParse/ TryFormat 大量使用
Span,是高性能编程的基础
💡 一句话总结:
Span 给你“零 GC 的切片工具”,stackalloc 给你“零 GC 的临时数组”,它们是现代 C# 高性能开发的黄金搭档。

388

被折叠的 条评论
为什么被折叠?



