Как интерпретировать результаты BenchmarkDotNet и dotMemory?
Итак, у меня есть следующий кусок кода в моем 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
,
Но когда я бегу BenchmarkDotnet
тест я получаю это
Я вижу, что у меня есть 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);
}
3 ответа
Я автор MemoryDiagnoser
и я также предоставил ответ на ваш вопрос в моем блоге. Я просто скопирую его здесь:
Как читать результаты
| Method | Gen 0 | Allocated |
|----------- |------- |---------- |
| A | - | 0 B |
| B | 1 | 496 B |
- Выделенный содержит размер выделенной управляемой памяти. Stackalloc / native heap распределения не включены. Это за один вызов, включительно.
Gen X
столбец содержит количествоGen X
сборов на 1 000 операций. Если значение равно 1, то это означает, что GC собирает память один раз на тысячу вызовов тестов в генерацииX
, BenchmarkDotNet использует некоторую эвристику при выполнении тестов, поэтому количество вызовов может быть разным для разных прогонов. Масштабирование делает результаты сопоставимыми.-
в столбце Gen означает, что сборка мусора не производилась.- Если
Gen X
столбца нет, значит, сборка мусора для генерации не производиласьX
, Если ни один из ваших тестов не вызывает GC, столбцы Gen отсутствуют.
Читая результаты, имейте в виду, что:
- 1 кБ = 1 024 байта
- Каждый экземпляр ссылочного типа имеет два дополнительных поля: заголовок объекта и указатель таблицы методов. Вот почему результаты всегда включают 2x размер указателя для каждого выделения объекта. Для получения более подробной информации о дополнительных затратах, пожалуйста, прочитайте этот отличный пост в блоге. Как на самом деле работает Object.GetType()? Конрад Кокоса.
- CLR выполняет выравнивание. Если вы попытаетесь выделить
new byte[7]
массив, он будет выделятьbyte[8]
массив.
То, что показывает BenchmarkDotNet, называется "Трафик памяти" в dotMemory. Запустите ваше приложение под dotMemory с включенным " Начать сбор данных о распределении немедленно". Получите снимок памяти в конце сеанса профилирования, затем откройте представление " Трафик памяти". Вы увидите все объекты, выделенные и собранные во время сеанса профилирования.
Как насчет вашего вопроса об узких местах в памяти, так как все выделенные объекты собраны, потребление памяти не растет, и вы не видите никаких проблем в dotMemory.
Но 3 ГБ трафика в 6 секунд довольно велики, и это может повлиять на производительность, используйте dotTrace (в режиме временной шкалы), чтобы увидеть, какая часть этих 6 секунд расходуется в GC.
Хорошо, давайте пройдемся по одной итерации цикла:
- Вы собираетесь выделить как минимум 1250 дюймов - так что давайте назовем это 5000 байтов или 5K.
- Вы создадите словарь, содержащий те же самые целые числа и 1250 строк со средней длиной, скажем, 8 символов - поэтому давайте назовем это 20000 байтов или 20 КБ. Плюс накладные расходы
Dictionary
сам. - затем
string.Join
собирается использоватьStringBuilder
- так что это минимум дополнительных 20 КБ (вероятно, больше, поскольку массив динамически измеряется). затемToString
будет вызван наStrinBuilder
(так еще 20К).
5К + 20К + 20К + 20К = 65К.
2,86 ГБ / 10000 = 0,286 МБ = около 286 КБ.
Итак, все это звучит примерно так. 65K - абсолютный минимум того, что может быть использование оперативной памяти. Фактор накладных расходов на конкатенацию строк при генерации значений словаря, накладные расходы на использование Dictionary
(дополнительные массивы, дополнительные копии int
и т. д.) и накладные расходы StringBuilder
(который, вероятно, выделяет большие массивы несколько раз из-за длины строки), и вы можете легко получить от 65 -> 286.