因此,我的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分钟后,我收到以下结果
该应用从10Mb
开始,一直到14Mb
。
我看到我已经分配了2.6GB
。什么?似乎一点也不好。另外,我看不到Gen1
和Gen2
列。这是否意味着代码没有在其中分配任何内容,所以没有任何显示?
如何解释结果?在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);
}
答案 0 :(得分:3)
我是MemoryDiagnoser
的作者,并且在我的blog上也提供了您问题的答案。我将在这里复制过去:
| Method | Gen 0 | Allocated |
|----------- |------- |---------- |
| A | - | 0 B |
| B | 1 | 496 B |
Gen X
列包含每个 1000 操作的Gen X
个集合的数量。如果该值等于1,则表示GC在生成X
时每千次基准调用收集一次内存。 BenchmarkDotNet在运行基准测试时会使用一些启发式方法,因此不同的运行次数可能会有所不同。缩放使结果具有可比性。-
表示未执行垃圾收集。Gen X
列,则表示没有为生成X
执行任何垃圾回收。如果您的基准测试均未引发GC,则“ Gen”列将不存在。阅读结果时请记住:
new byte[7]
数组,它将分配byte[8]
数组。答案 1 :(得分:1)
好的,让我们看一下单循环迭代:
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上。