如何解释BenchmarkDotNet和dotMemory的结果?

Sem*_*ble 3 .net c# benchmarking benchmarkdotnet dotmemory

所以,我在我接下来的一段代码Main()方法

for (int x = 0; x < 100; x++) // to mimic BenchmarkDotnet runs
   for (int y = 0; y < 10000; y++)
     LogicUnderTest();
Run Code Online (Sandbox Code Playgroud)

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

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

运行后Main()dotMemory了6分钟左右,我收到下面的结果

在此处输入图片说明

该应用程序从开始10Mb14Mb

但是当我进行BenchmarkDotnet测试时我得到了 在此处输入图片说明

我看到我已经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);
}
Run Code Online (Sandbox Code Playgroud)

Ada*_*nik 6

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

如何读取结果

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

阅读结果时,请记住:

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


mjw*_*lls 2

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

  • 您将至少分配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(额外数组、s 的额外副本int等)的开销以及StringBuilder(由于字符串的长度而可能多次分配大型数组的开销) ),你可以轻松地从 65 -> 286 得到。