C #: Точен ли этот класс тестирования? - PullRequest
35 голосов
/ 02 октября 2009

Я создал простой класс для сравнения некоторых моих методов. Но точно ли это? Я новичок в бенчмаркинге, сроках и т. Д., Поэтому подумал, что могу попросить кое-какие отзывы здесь Кроме того, если это хорошо, возможно, кто-то еще может использовать это также:)

public static class Benchmark
{
    public static IEnumerable<long> This(Action subject)
    {
        var watch = new Stopwatch();
        while (true)
        {
            watch.Reset();
            watch.Start();
            subject();
            watch.Stop();
            yield return watch.ElapsedTicks;
        }
    }
}

Вы можете использовать это так:

var avg = Benchmark.This(() => SomeMethod()).Take(500).Average();

Есть отзывы? Это выглядит довольно стабильно и точно, или я что-то пропустил?

Ответы [ 4 ]

21 голосов
/ 02 октября 2009

Это примерно так же точно, как вы можете получить для простого теста. Но есть некоторые факторы, которые не находятся под вашим контролем:

  • загрузка системы из других процессов
  • состояние кучи до / во время теста

Вы могли бы что-то сделать с этим последним пунктом, эталонный тест является одной из редких ситуаций, когда можно защитить GC.Collect. И вы можете позвонить subject один раз заранее, чтобы устранить любые проблемы JIT. Но для этого требуется, чтобы звонки на subject были независимыми.

public static IEnumerable<TimeSpan> This(Action subject)
{
    subject();     // warm up
    GC.Collect();  // compact Heap
    GC.WaitForPendingFinalizers(); // and wait for the finalizer queue to empty

    var watch = new Stopwatch();
    while (true)
    {
        watch.Reset();
        watch.Start();
        subject();
        watch.Stop();
        yield return watch.Elapsed;  // TimeSpan
    }
}

Для получения бонуса ваш класс должен проверить поле System.Diagnostics.Stopwatch.IsHighResolution . Если он выключен, у вас только очень грубое (20 мс) разрешение.

Но на обычном ПК с множеством служб, работающих в фоновом режиме, он никогда не будет очень точным.

10 голосов
/ 02 октября 2009

Проблемы с парой здесь.

Во-первых, помните, что при первом запуске кода транзитивное замыкание его вызовов методов будет сбито. Это означает, что первый прогон, вероятно, будет стоить дороже, чем каждый последующий прогон. В зависимости от того, тестируете ли вы «холодные» или «горячие» моменты времени, это может иметь значение. Я видел методы, в которых стоимость использования метода была выше, чем все остальные вызовы вместе взятые!

Во-вторых, помните, что сборщик мусора работает в другом потоке. Если вы производите мусор за один прогон, то затраты на его очистку могут быть не реализованы до последующих запусков. Поэтому вы не в состоянии учесть общую стоимость одного прогона, включив его в последующие прогоны.

Оба из них указывают на слабость всех сравнительных показателей: сравнительный анализ по своей природе нереален и, следовательно, имеет ограниченную ценность. В реальном коде GC будет работать, будет работать джиттер и так далее. Зачастую производительность в тестах сравнивается с реальной производительностью, потому что тест не учитывает изменчивость реальных затрат, присущих большой системе. Вместо того, чтобы анализировать характеристики производительности в отдельности, я предпочитаю смотреть на характеристики реалистичных сценариев, с которыми реально сталкиваются реальные клиенты.

7 голосов
/ 02 октября 2009

Вы должны обязательно вернуть ElapsedMilliseconds вместо ElapsedTicks. Значение, возвращаемое ElapsedTicks, зависит от частоты секундомера, которая может быть разной в разных системах. Он не обязательно будет соответствовать свойству Ticks объекта Timespan или DateTime.

См. http://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.elapsedticks.aspx.

Если вы действительно хотите получить дополнительное разрешение тиков, вы должны вернуть watch.Elapsed.Ticks (то есть Timestamp.Ticks) вместо watch.ElapsedTicks (это может быть один из самых тонких потенциальных ошибки в .Net). Из MSDN:

отметки секундомера отличаются от DateTime.Ticks. Каждый тик в Значение DateTime.Ticks представляет один 100-наносекундный интервал. Каждый тик в значение ElapsedTicks представляет интервал времени, равный 1 секунде делится на частоту.

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

И последнее замечание, которое, вероятно, не относится к большинству применений этого класса: секундомер работает немного быстрее по сравнению с системным временем. На моем компьютере он опережает примерно на 5 секунд (это секунд , а не миллисекунд) через 24 часа, а на других машинах этот дрейф может быть еще больше. Поэтому немного вводить в заблуждение говорить, что он очень точен , тогда как на самом деле он очень гранулирован . Для временных методов короткой продолжительности это, очевидно, не будет существенной проблемой.

И еще один последний момент, который, безусловно, имеет отношение к : я часто замечал, что во время бенчмаркинга я получал кучу времени выполнения, которые все сгруппированы в узком диапазоне значений (например, 80 , 80, 79, 82 и т. Д.), Но иногда в Windows происходит что-то еще (например, открытие другой программы или мой антивирус запускается, или что-то в этом роде), и я получаю дикое значение с другими (например, 80 80, 79, 271, 80 и т. Д.). Я думаю, что простым решением этой проблемы является использование медианы ваших измерений вместо среднего . Я не знаю, поддерживает ли Linq это автоматически или нет.

2 голосов
/ 02 октября 2009

Поскольку я не программист на C #, я не могу сказать с какой-либо точностью, является ли этот класс подходящей реализацией для подсчета того, сколько времени занимает выполнение функции. Однако, есть вещи, которые нужно иметь в виду для повторяемости и точности.

Я не разбираюсь в различных тонкостях .NET Framework, но в зависимости от того, как он компилируется в нативный код, может быть, что любая компиляция повлияет на результаты тестов. Кроме того, наличие или отсутствие функции в кеше также может иметь значение. Таким образом, вы захотите зациклить свою функцию, чтобы убедиться, что нет компиляции и что все загружено и готово. Как только это будет сделано, вы сможете начать.

У других, вероятно, будет больше информации и знаний о .NET, чем у меня.

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