如何解释BenchmarkDotNet和dotMemory的结果?

问题描述 投票:1回答:3

所以,我在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);
}
c# .net benchmarking dotmemory benchmarkdotnet
3个回答
4
投票

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

如何阅读结果

|     Method |  Gen 0 | Allocated |
|----------- |------- |---------- |
|          A |      - |       0 B |
|          B |      1 |     496 B |
  • Allocated包含分配的托管内存的大小。不包括Stackalloc /本机堆分配。这是每次调用,包括在内。
  • Gen X列包含每1000次操作的Gen X集合数。如果该值等于1,则意味着GC在生成X中每千个基准调用收集一次内存。 BenchmarkDotNet在运行基准测试时使用了一些启发式算法,因此不同运行的调用次数可能不同。缩放使结果具有可比性。
  • Gen列中的-表示没有执行垃圾收集。
  • 如果Gen X列不存在,则表示没有为生成X执行垃圾收集。如果您的基准测试都没有导致GC,则Gen列不存在。

阅读结果时请记住:

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

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(额外数组,ints的额外副本等)的开销和StringBuilder的开销(由于长度的原因,可能会多次分配大型数组)字符串)你可以很容易地得到65 - > 286。


1
投票

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

关于内存瓶颈的问题怎么样呢,因为收集了所有分配的对象,内存消耗没有增长,你在dotMemory中看不到任何问题。

但是每6秒3GB的流量是相当大的并且可能会对性能产生影响,使用dotTrace(在时间线模式下)可以看到这6秒的哪一部分花费在GC上。

© www.soinside.com 2019 - 2024. All rights reserved.