[英]C# foreach performance vs 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 分析器,缓存未命中似乎要高得多
有几个影响。
当你有你的线路public int[] value1 = new int[80];
,您有一个额外的内存分配:在堆上创建一个新数组,该数组将容纳 80 个整数(320 字节)+ 类的开销。 您进行了 500 000 次这些分配。
这些分配总计超过 160 MB 的 RAM,这可能会导致 GC 启动并查看是否有内存要释放。
此外,当您分配如此多的内存时,列表中的某些对象可能不会保留在 CPU 缓存中。 当您稍后枚举集合时,CPU 可能需要从 RAM 中读取数据,而不是从缓存中读取数据,这会导致严重的性能损失。
我无法重现两者之间的巨大差异,我也不指望它。 下面是我在 .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;
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.