我正在尝试了解内存分配以及如何减少它们。
我创建了以下长列表进行测试
var list = new List<int>(Enumerable.Range(0, 1_000_000).ToArray());
然后我循环遍历它并打印了这样的字符串
for (var i = 0; i < list.Count; i++)
{
Console.WriteLine("Item # " + list[i]);
}
上一个循环生成了超过 200 万个分配。我相信
"Item # " + list[i]
行产生了 200 万个分配。
** 问题 1** 在这种情况下为什么要分配 200 万个字符串?
list[i]
是否必须作为字符串存储在堆中,这是 1 次分配,然后是另一个分配中的组合字符串,因此每个循环 2 次分配?
下一步我考虑使用字符串生成器来减少分配
var builder = new StringBuilder();
for (var i = 0; i < list.Count; i++)
{
builder.Append("Item # ");
builder.Append(list[i]);
Console.WriteLine(builder.ToString());
builder.Clear();
}
上面的代码有100万个分配,是之前分配的一半。
但是,仍然有很多分配。
我知道字符串是 C# 中的不可变对象,每个字符串在堆中都有自己的分配。但是,有没有一种方法可以重用内存分配,以便我们可以创建 1 个字符串,然后在该循环内一遍又一遍地重用相同的分配?就我而言,一旦打印了字符串,我就不再需要它了。对我来说,重用相同的分配并更新它的值是安全的。
** 问题2** 是否可以重复使用内存分配来减少分配量?
** 问题 3** 我还可以尝试哪些其他技巧来改善我的循环?
** 问题1** 为什么在这种情况下会分配200万个字符串? list[i] 是否必须作为字符串存储在堆中,这是 1 次分配,然后是另一个分配中的组合字符串,因此每个循环 2 次分配?
表演
"Item # " + list[i]
进行两次分配:它从
list[i]
整数创建一个字符串,然后通过将常量 "Item #"
与之前收到的中间值连接起来创建另一个字符串。
基于
StringBuilder
的版本消除了 list[i]
到 string
的转换,并将其直接写入其后备缓冲区。
**问题2**是否可以重复使用内存分配来减少分配量?
是的,这就是
StringBuilder
的作用。不过,只要你想取出一个string
,就需要分配。
您可以通过分配单个字符数组来进一步重用内存使用情况,但随后您还必须处理内容的实际长度。
或者根本不分配并按照评论的建议使用
Console.Write
。
** 问题 3** 我还可以尝试哪些其他技巧来改进我的循环?
见上文。这取决于您想做什么。