Как интерпретировать результаты BenchmarkDotNet и dotMemory? - PullRequest
0 голосов
/ 07 сентября 2018

Итак, у меня есть следующий фрагмент кода в моем 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. Какие? Кажется, не очень хорошо. Кроме того, я не вижу столбцы 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 ]

0 голосов
/ 10 сентября 2018

То, что показывает BenchmarkDotNet, называется «Трафик памяти» в dotMemory. Запустите ваше приложение под dotMemory с включенным « Начать сбор данных о распределении немедленно ». Получите снимок памяти в конце сеанса профилирования, затем откройте представление « Трафик памяти ». Вы увидите все объекты, выделенные и собранные во время сеанса профилирования.

Как насчет вашего вопроса об узких местах в памяти, поскольку все выделенные объекты собраны, потребление памяти не увеличивается, и вы не видите никаких проблем в dotMemory.

Но 3 ГБ трафика в 6 секунд довольно велики, и это может повлиять на производительность, используйте dotTrace (в режиме временной шкалы), чтобы увидеть, какая часть этих 6 секунд расходуется в GC.

0 голосов
/ 10 сентября 2018

Я являюсь автором MemoryDiagnoser, и я также предоставил ответ на ваш вопрос в моем блоге . Я просто скопирую его здесь:

Как читать результаты

|     Method |  Gen 0 | Allocated |
|----------- |------- |---------- |
|          A |      - |       0 B |
|          B |      1 |     496 B |
  • Allocated содержит размер выделенной управляемой памяти. Stackalloc / выделение собственной кучи не включены. Это за один вызов, включительно .
  • Столбец Gen X содержит количество Gen X коллекций на 1 000 операций. Если значение равно 1, то это означает, что GC собирает память один раз на тысячу вызовов тестов в поколении X. BenchmarkDotNet использует некоторую эвристику при выполнении тестов, поэтому количество вызовов может быть разным для разных прогонов. Масштабирование делает результаты сопоставимыми.
  • - в столбце Gen означает, что сборка мусора не производилась.
  • Если столбец Gen X отсутствует, это означает, что сборка мусора для генерации X не производилась. Если ни один из ваших тестов не вызывает GC, столбцы Gen отсутствуют.

При чтении результатов имейте в виду, что:

  • 1 кБ = 1 024 байта
  • Каждый экземпляр ссылочного типа имеет два дополнительных поля: заголовок объекта и указатель таблицы методов. Вот почему результаты всегда включают 2x размер указателя для каждого размещения объекта. Для получения более подробной информации о дополнительных затратах, пожалуйста, прочитайте этот великолепный пост в блоге Как на самом деле работает Object.GetType ()? от Konrad Kokosa.
  • CLR выполняет выравнивание. Если вы попытаетесь выделить массив new byte[7], он выделит массив byte[8].
0 голосов
/ 08 сентября 2018

ОК, давайте пройдемся по одной итерации цикла:

  • Вы собираетесь выделить , по крайней мере, 1250 дюймов - поэтому давайте назовем это 5000 байтов или 5K.
  • Вы создадите словарь, содержащий те же самые целые и 1250 строк со средней длиной, скажем, 8 символов - поэтому давайте назовем это 20000 байтов или 20 КБ. Плюс накладные расходы самой Dictionary.
  • Тогда string.Join собирается использовать StringBuilder - так что это минимум дополнительных 20 КБ (вероятно, больше, поскольку массив динамически измеряется). Тогда ToString будет вызван на StrinBuilder (то есть еще на 20К).

5K + 20K + 20K + 20K = 65K.

2,86 ГБ / 10 000 = 0,286 МБ = около 286 КБ.

Итак, все это звучит о праве. 65K - абсолютный минимум того, что может быть использование оперативной памяти. Фактор накладных расходов на конкатенацию строк при генерации значений словаря, накладные расходы на использование Dictionary (дополнительные массивы, дополнительные копии int s и т. Д.) И накладные расходы на StringBuilder (что, вероятно, выделяет большие массивы для числа раз из-за длины строки), и вы можете легко получить от 65 -> 286.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...