3 .net c# recursion garbage-collection
如果你按原样运行它会很快执行而不会占用内存.如果你取消注释不好,它会变慢并最终锁定,如果没有内存异常.
为什么GC在递归函数中似乎失败?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Repo
{
class Program
{
static Random rng = new Random(42);
static void Main(string[] args)
{
new Thread(Main2, 50 * 1024 * 1024).Start(); //Increase stack size
}
static void Main2()
{
//Bad(0, "test");
var ls = Good(0, "test");
while(ls.Any())
{
var v = ls.First();
ls.AddRange(Good(v.Item1, v.Item2));
ls.RemoveAt(0);
}
}
class Foo
{
public byte[] data;
public Foo(int size) { data = new byte[size]; }
}
static List<Tuple<int, string>> Good(int a, string b)
{
if (a >= 5000000)
return new List<Tuple<int, string>>();
Console.WriteLine("{0}", a);
var ls = new List<Tuple<int, string>>();
{
var data = new byte[rng.Next(1024, 1024 * 20)]; //This line eats up all the memory
ls.Add(Tuple.Create(a + 1, ASCIIEncoding.Default.GetString(data, 128, 64)));
}
return ls;
}
static void Bad(int a, string b)
{
if (a >= 5000000)
return;
Console.WriteLine("{0}", a);
var ls = new List<Tuple<int, string>>();
{
var data = new byte[rng.Next(1024, 1024 * 20)]; //This line eats up all the memory
ls.Add(Tuple.Create(a+1, ASCIIEncoding.Default.GetString(data, 128, 64)));
}
foreach(var v in ls)
{
Bad(v.Item1, v.Item2);
}
return;
}
}
}
Run Code Online (Sandbox Code Playgroud)
在Good()版本中,您分配ls一次,向其添加一个相当小的元组,并Good()返回给调用者.
在该Bad()版本中,初始调用会Bad()产生许多递归调用Bad(),这反过来可以产生许多自己的递归调用.您将继续ls在每次迭代中创建新实例Bad(),并继续添加新元组.您的内存分析器应该向您显示List<Tuple<int, string>>该Bad()案例中的类型使用了更多字节.它不是byte[] data,它应该超出范围.
在ls给定的递归调用完成之前,无法收集引用.
JIT可以执行生命周期分析并确定可以在超出范围之前收集局部变量,因此不是您的数组必须以其堆栈帧为根.
但是,在调试版本或调试器下运行时,GC更加保守(如果您想在调试会话期间检查值).在调试器外部运行发布版本,您将看到更加渐进的内存增加,可以通过对列表的生成引用和递归调用中的元组来解释.
| 归档时间: |
|
| 查看次数: |
398 次 |
| 最近记录: |