当程序创建大量对象时,C#应用程序CPU性能急剧下降

当程序创建大量对象时,C#应用程序CPU性能急剧下降,c#,performance,C#,Performance,我遇到了一个奇怪的性能问题: 我有一个C#应用程序,它可以创建数百万个C#对象 在代码的一个不相关部分中,应用程序执行特定的工作,该工作不依赖于在步骤1中分配的数据 CPU时间似乎与步骤1中创建的对象数量相关 我写了一个简单的案例,再现了我的问题。 在调用DoMyWork()方法之前,使用创建的数以百万计的字符串对象数调用slood命令。 如您所见,如果实例化了2亿个字符串,则相同的DoMyWork()方法可能需要3秒钟的时间 我是否遗漏了语言中的某些内容? 假设未达到物理内存限制,是否存在不

我遇到了一个奇怪的性能问题:

  • 我有一个C#应用程序,它可以创建数百万个C#对象

  • 在代码的一个不相关部分中,应用程序执行特定的工作,该工作不依赖于在步骤1中分配的数据

  • CPU时间似乎与步骤1中创建的对象数量相关

    我写了一个简单的案例,再现了我的问题。 在调用
    DoMyWork()
    方法之前,使用创建的数以百万计的字符串对象数调用
    slood
    命令。 如您所见,如果实例化了2亿个字符串,则相同的
    DoMyWork()
    方法可能需要3秒钟的时间

    • 我是否遗漏了语言中的某些内容?
    • 假设未达到物理内存限制,是否存在不应达到的最大对象数,否则CLR将减速?
    我在英特尔Core i7-6700上的Windows 10下运行了我的测试,我的程序是在32位模式下构建的控制台版本(VS 2017-fw 4.6.1):

    减速0 分配40000个哈希表:2毫秒 分配40000个哈希表:4毫秒 分配40000个哈希表:15毫秒 分配40000个哈希表:2毫秒 分配40000个哈希表:5毫秒 分配40000个哈希表:5毫秒 分配40000个哈希表:2毫秒 分配40000个哈希表:18毫秒 分配40000个哈希表:10毫秒 分配40000个哈希表:19毫秒 减速0使用约3000万

    slowdown 200 Allocating 40000 hashtables: 392 ms Allocating 40000 hashtables: 1120 ms Allocating 40000 hashtables: 3067 ms Allocating 40000 hashtables: 2 ms Allocating 40000 hashtables: 31 ms Allocating 40000 hashtables: 418 ms Allocating 40000 hashtables: 15 ms Allocating 40000 hashtables: 2 ms Allocating 40000 hashtables: 18 ms Allocating 40000 hashtables: 416 ms 减速200 分配40000个哈希表:392毫秒 分配40000个哈希表:1120毫秒 分配40000个哈希表:3067毫秒 分配40000个哈希表:2毫秒 分配40000个哈希表:31毫秒 分配40000个哈希表:418毫秒 分配40000个哈希表:15毫秒 分配40000个哈希表:2毫秒 分配40000个哈希表:18毫秒 分配40000个哈希表:416毫秒 减速200使用~800M


    使用系统;
    使用系统诊断;
    使用系统集合;
    名称空间减速
    {
    班级计划
    {
    静态字符串[]arr;
    静态空心CreateHugeStringArray(长尺寸)
    {
    arr=新字符串[大小*1000000];
    对于(int i=0;i
    很可能是垃圾收集器的讨厌东西,它会冻结主线程,即使它主要在后台线程上工作,如下所述:


    如果您收集它,无论“不相关”数组的大小如何,时间都保持在90毫秒左右(在我的例子中)。

    该问题是由于垃圾收集器与您的
    DoMyWork同时运行造成的。它需要清理的阵列的绝对大小会“中断”实际工作

    要查看GC的影响,请在
    StartNew
    调用之前添加以下行-以便GC工作在计时之前发生:

    GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
    GC.Collect();
    

    以下代码创建10000个新字符串对象,强制运行垃圾收集:

     string str = "";
    
     for (int i = 0; i < 10000; i++) str += i;
    
    string str=”“;
    对于(inti=0;i<10000;i++)str++=i;
    
    垃圾收集器的性能与

    • 已分配的对象数
    • 正在使用的内存总量
    CreateHugeStringArray()分配非常大的对象,从而增加了使用的内存总量。在极端情况下,部分内存可能在磁盘上(被调出),从而进一步降低系统速度


    你的故事的寓意是——除非你需要内存,否则不要分配内存。

    还没有找到原因,但似乎LOH中有一个巨大的数组会显著降低垃圾收集的速度。 但是,如果我们创建许多较小的数组来保存相同数量的数据(这将进入第2代而不是LOH),GC不会慢很多。似乎带有1kk字符串指针的数组占用了大约400万字节的内存。因此,为了避免到达LOH,数组必须占用少于85 KB的空间。这大约要少50倍。您可以使用旧技巧将大数组拆分为多个小数组

        private static string[][] arrayTwoDimentional;
    
        private static int _arrayLength = 1000000;
    
        private static int _sizeFromExample = 200;
    
        static void CreateHugeStringArrayTwoDimentional()
        {
            // Make 50 times more smaller arrays
            arrayTwoDimentional = new string[_sizeFromExample * 50][];
    
            for (long i = 0; i < arrayTwoDimentional.Length; i++)
            {
                // Make array smaller 50 times
                arrayTwoDimentional[i] = new string[_arrayLength / 50];
                for (var index = 0; index < arrayTwoDimentional[i].Length; index++)
                {
                    arrayTwoDimentional[i][index] = "";
                }
            }
        }
    
        static string GetByIndex(long index)
        {
            var arrayLenght = _arrayLength / 50;
            var firstIndex = index / arrayLenght;
            var secondIndex = index % arrayLenght;
    
            return arrayTwoDimentional[firstIndex][secondIndex];
        }
    
    私有静态字符串[][]数组二维;
    私有静态int_arraylelength=1000000;
    私有静态int_sizeFromExample=200;
    静态void CreateHugeStringArray二维()
    {
    //使阵列更小50倍
    ArrayTwoDimensional=新字符串[_SizeFromeExample*50]];
    for(长i=0;i
    证明GC是这里的瓶颈

    替换阵列布局后

    在本例中,数组大小是硬编码的。Codeproject中有一个很好的例子,说明如何计算类型存储对象的大小,这将有助于调整数组的大小:

    在您的示例中,您不会创建“数百万个C#对象”。您创建了一个庞大的字符串数组,每个数组元素实际上都指向同一个字符串对象(请参见string)
     string str = "";
    
     for (int i = 0; i < 10000; i++) str += i;
    
        private static string[][] arrayTwoDimentional;
    
        private static int _arrayLength = 1000000;
    
        private static int _sizeFromExample = 200;
    
        static void CreateHugeStringArrayTwoDimentional()
        {
            // Make 50 times more smaller arrays
            arrayTwoDimentional = new string[_sizeFromExample * 50][];
    
            for (long i = 0; i < arrayTwoDimentional.Length; i++)
            {
                // Make array smaller 50 times
                arrayTwoDimentional[i] = new string[_arrayLength / 50];
                for (var index = 0; index < arrayTwoDimentional[i].Length; index++)
                {
                    arrayTwoDimentional[i][index] = "";
                }
            }
        }
    
        static string GetByIndex(long index)
        {
            var arrayLenght = _arrayLength / 50;
            var firstIndex = index / arrayLenght;
            var secondIndex = index % arrayLenght;
    
            return arrayTwoDimentional[firstIndex][secondIndex];
        }