如何解释BenchmarkDotNet和dotMemory的结果?

时间:2018-09-07 13:33:56

标签: c# .net benchmarking dotmemory benchmarkdotnet

因此,我的Main()方法中包含以下代码

for (int x = 0; x < 100; x++) // to mimic BenchmarkDotnet runs
   for (int y = 0; y < 10000; y++)
     LogicUnderTest();

接下来,我正在测试以下课程

[MemoryDiagnoser, ShortRunJob]
public class TestBenchmark
{
    [Benchmark]
    public void Test_1()
    {
        for (int i = 0; i < 10000; i++)
            LogicUnderTest();
    }
}

Main()下运行dotMemory 6分钟后,我收到以下结果

enter image description here

该应用从10Mb开始,一直到14Mb

但是当我运行BenchmarkDotnet测试时,我得到了 enter image description here

我看到我已经分配了2.6GB。什么?似乎一点也不好。另外,我看不到Gen1Gen2列。这是否意味着代码没有在其中分配任何内容,所以没有任何显示?

如何解释结果?在DotMemory中似乎完全可以,但是在BenchmarkDotNet中则不可行。我是BenchmarkDotnet的新手,将对与结果有关的任何信息有所帮助。

PS。 LogicUnderTest()广泛用于字符串。

PSS。大致上,LogicUnderTest是这样实现的:

void LogicUnderTest()
{
    var dict = new Dictionary<int, string>();
    for (int j = 0; j < 1250; j++)
        dict.Add(j, $"index_{j}");
    string.Join(",", dict.Values);
}

3 个答案:

答案 0 :(得分:3)

我是MemoryDiagnoser的作者,并且在我的blog上也提供了您问题的答案。我将在这里复制过去:

如何读取结果

|     Method |  Gen 0 | Allocated |
|----------- |------- |---------- |
|          A |      - |       0 B |
|          B |      1 |     496 B |
  • 已分配包含已分配的托管内存的大小。 不包括Stackalloc /本地堆分配。是按包含的单次调用。
  • Gen X列包含每个 1000 操作的Gen X个集合的数量。如果该值等于1,则表示GC在生成X时每千次基准调用收集一次内存。 BenchmarkDotNet在运行基准测试时会使用一些启发式方法,因此不同的运行次数可能会有所不同。缩放使结果具有可比性。
  • “ Gen”列中的
  • -表示未执行垃圾收集。
  • 如果没有Gen X列,则表示没有为生成X执行任何垃圾回收。如果您的基准测试均未引发GC,则“ Gen”列将不存在。

阅读结果时请记住:

  • 1 kB = 1,024字节
  • 每个引用类型实例都有两个额外的字段:对象标头和方法表指针。这就是为什么结果总是为每个对象分配包括2倍的指针大小。有关额外开销的更多详细信息,请阅读Konrad Kokosa撰写的精彩博文How does Object.GetType() really work?
  • CLR进行一些调整。如果您尝试分配new byte[7]数组,它将分配byte[8]数组。

答案 1 :(得分:1)

好的,让我们看一下单循环迭代:

  • 您将至少分配 1250个整数-因此我们将其称为5000字节或5K。
  • 您将创建一个字典,其中包含相同的整数和1250个字符串,平均长度为8个字符-因此,我们将其称为20000字节或20K。 加上Dictionary本身的开销。
  • 然后string.Join将使用StringBuilder-因此,那里至少要多出20K(随着动态调整数组的大小,可能会更多)。然后,将在ToString上调用StrinBuilder(因此又需要20K)。

5K + 20K + 20K + 20K = 65K。

2.86GB / 10,000 = 0.286MB =约286k。

所以,所有听起来都是正确的。 65K是RAM使用量的绝对最小值。在生成字典值时考虑字符串串联开销,使用Dictionary的开销(额外的数组,int s的额外副本等)和StringBuilder的开销(这是可能由于字符串的长度而多次分配大数组),您可以轻松地从65-> 286获得。

答案 2 :(得分:1)

BenchmarkDotNet向您显示的内容在dotMemory中称为“内存流量”。在启用了“ Start collecting allocation data immediately”的dotMemory下运行您的应用程序。在分析会话结束时获取内存快照,然后打开“ Memory Traffic”视图。您将在性能分析会话期间看到所有分配和收集的对象。

关于内存瓶颈的问题呢,由于所有分配的对象都已收集,因此内存消耗不会增加,并且在dotMemory中看不到任何问题。

但是每6秒3GB的流量是相当大的,并且可能会影响性能,请使用dotTrace(在时间轴模式下)查看这6秒中有多少时间用在GC上。