C#表现好奇心

时间:2013-08-10 18:23:58

标签: c# performance

对下面的程序非常好奇(是的,在没有附带调试器的情况下在发布模式下运行),第一个循环为数组的每个元素分配一个新对象,并且需要大约一秒钟才能运行。

所以我想知道哪个部分占用了最多的时间 - 对象创建或分配。所以我创建了第二个循环来测试创建对象所需的时间,第三个循环来测试分配时间,并且都在几毫秒内运行。发生了什么事?

static class Program
{
    const int Count = 10000000;

    static void Main()
    {
        var objects = new object[Count];
        var sw = new Stopwatch();
        sw.Restart();
        for (var i = 0; i < Count; i++)
        {
            objects[i] = new object();
        }
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds); // ~800 ms
        sw.Restart();
        object o = null;
        for (var i = 0; i < Count; i++)
        {
            o = new object();
        }
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds); // ~ 40 ms
        sw.Restart();
        for (var i = 0; i < Count; i++)
        {
            objects[i] = o;
        }
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds); // ~ 50 ms
    }
}

2 个答案:

答案 0 :(得分:15)

当创建占用少于85,000字节RAM且不是double数组的对象时,它被放置在称为Generation Zero堆的内存区域中。每当Gen0堆增长到一定大小时,系统可以找到实时引用的Gen0堆中的每个对象都被复制到Gen1堆;然后批量擦除Gen0堆,以便有更多新对象的空间。如果Gen1堆达到一定大小,那么存在引用的所有内容都将被复制到Gen2堆中,从而可以批量擦除Gen0堆。

如果创建并立即放弃了许多对象,Gen0堆将重复填充,但Gen0堆中的极少数对象必须复制到Gen1堆。因此,如果有的话,Gen1堆将非常缓慢地填充。相反,如果Gen0堆中的大多数对象仍然在Gen0堆已满时被引用,则系统必须将这些对象复制到Gen1堆。这将迫使系统花费时间复制这些对象,并且Gen1堆也可能填满足够的以至于必须扫描实时对象,并且那里的所有活动对象将不得不再次复制到Gen2堆。所有这些都需要更多时间。

在第一次测试中减慢事情的另一个问题是,当尝试识别所有实时Gen0对象时,系统可以忽略任何Gen1或Gen2对象,只有在自上一代Gen0集合以来尚未触及它们时。在第一个循环期间,将objects数组不断触摸;因此,每个Gen0集合都必须花时间处理它。在第二个循环期间,它根本没有被触及,所以即使将有尽可能多的Gen0集合,它们也不会花费很长时间来执行。在第三个循环期间,将不断触摸数组,但不会创建新的堆对象,因此不需要垃圾收集周期,并且它们将花费多长时间并不重要。

如果你要添加第四个循环,它在每次传递中创建并放弃了一个对象,但是也存储在一个数组槽中,它是对一个预先存在的对象的引用,我希望它需要比合并时间更长的时间第二和第三循环,即使它将执行相同的操作。可能没有第一个循环那么多的时间,因为很少有新创建的对象需要从Gen0堆中复制出来,但是比第二个循环更长,因为需要额外的工作来确定哪些对象仍然存在。如果你想进一步探究一些事情,用嵌套循环进行第五次测试可能会很有趣:

for (int ii=0; ii<1024; ii++)
  for (int i=ii; i<Count; i+=1024)
     ..

我不知道确切的细节,但.NET试图通过将它们细分为块来扫描整个大型数组,其中只有一小部分被触摸。如果触摸了大块的大块,则必须扫描该块中的所有引用,但是可以忽略存储在自上一代Gen0集合以来未被触摸的块中的引用。如上所示断开循环可能会导致.NET最终触及Gen0集合之间数组中的大部分块,很可能比第一个循环产生更慢的时间。

答案 1 :(得分:14)

  1. 您创建 1000万对象并将其存储在不同的位置 在记忆中。这里的内存消耗最高。
  2. 您创建了 1000万个对象,但它们并未存储在任何位置, 刚丢弃
  3. 您创建 1个对象并为其创建 1000万个引用 记忆消耗。
  4. 是的,性能分析低于 10万对象(1000万将花费太长时间)。

    Performance for ONLY 10 thousand objects

    UPDATE:此图显示了第一种情况下内存分配的CPU工作。注意JIT_New@@...函数占用80.5%的CPU时间。

    CPU performance case 1

    UPDATE2:以及CaseTwo的完整CPU时间。

    CPU performance case 2

    UPDATE3:仅为完整性,第三种情况

    CPU performance case 3