从 C# 基础到掌握 — 第 9 集
了解 GC 的性能、值类型和分配成本
该博客上撰写的文章是出于教学目的而创建的。 另外,文章中的信息可能不正确,请谨慎。
在 C# 中,GC 管理内存,但分配成本并没有消失。 特别是在恒定帧时间很重要的程序中(例如游戏),您需要知道对象何时创建。
值类型和引用类型
public readonly struct Damage
{
public int Value { get; }
public Damage(int value) => Value = value;
}
Damage first = new(10);
Damage second = first;
值类型包含变量中的值本身,并在赋值时被复制。 对于引用类型,将复制指向托管堆中对象的引用。
不要只记住值类型总是在堆栈上而引用类型总是很慢。 实际存储位置和成本将根据使用环境、大小和运行时优化而有所不同。
避免拳击
int score = 100;
object boxed = score;
int restored = (int)boxed;
当值类型被视为对象或实现的接口时,可能会发生装箱。 使用泛型集合,您可以存储值类型,而无需将其更改为对象。
var scores = new List<int>();
scores.Add(100);
减少重复分配
private readonly List<Monster> visibleMonsters = new(128);
void CollectVisible()
{
visibleMonsters.Clear();
foreach (Monster monster in monsters)
{
if (monster.IsVisible)
{
visibleMonsters.Add(monster);
}
}
}
通过重用具有容量的缓冲区而不是每次创建新列表,可以减少分配数量。 字符串连接、捕获 lambda 和 LINQ 结果生成也会导致迭代路径中的分配。
先测量
GC的生成和收集方法由运行时管理,短命对象不一定是问题。 以牺牲可读性为代价预先优化所有代码只能增加维护成本。
您应该使用分析器来确定配额和停顿是否是真正的瓶颈,然后应用池化、缓冲区重用和值键入。 性能优化不是猜测语法的任务,而是减少测量成本原因的任务。