C#foreach性能与内存碎片

时间:2019-01-16 06:54:45

标签: c# performance memory fragmentation

跟踪性能问题(我知道是微问题)我以该测试程序结束。使用框架4.5和发布模式进行编译,它在我的计算机上花费了大约10毫秒的时间。

如果我删除此行,会困扰我什么

public int[] value1 = new int[80];

时间接近2毫秒。似乎存在一些内存碎片问题,但我无法解释原因。我已经用Net Core 2.0测试了该程序,并且结果相同。谁能解释这种行为?

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace ConsoleApp4
{

    public class MyObject
    {
        public int value = 1;
        public int[] value1 = new int[80];
    }


    class Program
    {
        static void Main(string[] args)
        {

            var list = new List<MyObject>();
            for (int i = 0; i < 500000; i++)
            {
                list.Add(new MyObject());
            }

            long total = 0;
            for (int i = 0; i < 200; i++)
            {
                int counter = 0;
                Stopwatch timer = Stopwatch.StartNew();

                foreach (var obj in list)
                {
                    if (obj.value == 1)
                        counter++;
                }

                timer.Stop();
                total += timer.ElapsedMilliseconds;
            }

            Console.WriteLine(total / 200);

            Console.ReadKey();
        }
    }
}

更新:

经过一些研究,我得出的结论是,这只是处理器缓存访问时间。使用VS事件探查器,缓存未命中率似乎要高得多

  • 没有数组

Without array

  • 使用数组

With array

2 个答案:

答案 0 :(得分:1)

涉及多个方面。

当行public int[] value1 = new int[80];时,您将有额外的内存分配:在堆上创建一个新数组,该数组可容纳80个整数(320个字节)+类的开销。您需要进行500,000个分配。

这些分配总共占用了160 MB以上的RAM,这可能会导致GC启动并查看是否有内存要释放。

此外,当您分配了如此多的内存时,列表中的某些对象很可能没有保留在CPU缓存中。当您以后枚举集合时,CPU可能需要从RAM中读取数据,而不是从缓存中读取数据,这将导致严重的性能损失。

答案 1 :(得分:1)

我无法重现两者之间的巨大差异,我也不会期望如此。以下是我在.NET Core 2.2上获得的结果。

MyObject的实例将在堆上分配。在一种情况下,您有一个int和对该int数组的引用。在另一个中,您只有int。在这两种情况下,您都需要执行其他操作,以遵循列表中的引用。两种情况都是相同的,编译后的代码显示了这一点。

分支预测将影响其运行速度,但是由于您每次都在相同条件下进行分支,因此我不希望每次运行都会改变(除非您更改数据)。

BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.376 (1803/April2018Update/Redstone4)
Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=2.2.200-preview-009648
  [Host]     : .NET Core 2.2.0 (CoreCLR 4.6.27110.04, CoreFX 4.6.27110.04), 64bit RyuJIT
  DefaultJob : .NET Core 2.2.0 (CoreCLR 4.6.27110.04, CoreFX 4.6.27110.04), 64bit RyuJIT


       Method |   size |     Mean |     Error |    StdDev | Ratio |
------------- |------- |---------:|----------:|----------:|------:|
    WithArray | 500000 | 8.167 ms | 0.0495 ms | 0.0463 ms |  1.00 |
 WithoutArray | 500000 | 8.167 ms | 0.0454 ms | 0.0424 ms |  1.00 |

供参考:

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Collections.Generic;

namespace CoreSandbox
{
    [DisassemblyDiagnoser(printAsm: true, printSource: false, printPrologAndEpilog: true, printIL: false, recursiveDepth: 1)]
    //[MemoryDiagnoser]
    public class Test
    {
        private List<MyObject> dataWithArray;
        private List<MyObjectLight> dataWithoutArray;

        [Params(500_000)]
        public int size;

        public class MyObject
        {
            public int value = 1;
            public int[] value1 = new int[80];
        }

        public class MyObjectLight
        {
            public int value = 1;
        }

        static void Main(string[] args)
        {
            var summary = BenchmarkRunner.Run<Test>();
        }

        [GlobalSetup]
        public void Setup()
        {
            dataWithArray = new List<MyObject>(size);
            dataWithoutArray = new List<MyObjectLight>(size);

            for (var i = 0; i < size; i++)
            {
                dataWithArray.Add(new MyObject());
                dataWithoutArray.Add(new MyObjectLight());
            }
        }

        [Benchmark(Baseline = true)]
        public int WithArray()
        {
            var counter = 0;

            foreach(var obj in dataWithArray)
            {
                if (obj.value == 1)
                    counter++;
            }

            return counter;
        }

        [Benchmark]
        public int WithoutArray()
        {
            var counter = 0;

            foreach (var obj in dataWithoutArray)
            {
                if (obj.value == 1)
                    counter++;
            }

            return counter;
        }

    }
}