Бенчмаркинг в таблице C # ASCII в BrainfuckUnit работы и репозитория с платформой Entity Framework 6 Теперь уже работает FizzBuzz? Признак доступа к переменным классов с использованием itshared_ptr и FILE для переноса cstdio (обновление: также dlfcn.h) Thread-Safe и Lock-Free - Queue РеализацияИнтервью для кодирования: Fizz BuzzRaspberry PI управляемый автомобиль (код для 6-летнего) Переводчик Charmander Brainfuck в HaskellSimon Говорит: «Сделайте мне симпатичную игру». Мы будем считать звезды. Еще один синтаксический анализатор C ++ JSON. Эффективное возведение в квадрат каждого элемента в отсортированном массиве , сохраняя его отсортированным генератором изображений и средством просмотра. Релевантность в «Ростом Полотенце взаимного понимания». Группировка элементов в массиве с помощью нескольких свойств. Запись музыки на компьютере в WAV-файл в C16-битном FizzBuzz в x86. Сборник NASM. Набор для создания вопросов CodeReview. Определение минимального скалярного продукта с использованием Игра ST MonadConway's Life в C ++ Экспорт типов документов с использованием очередей и многопоточностиSHOUTY_SNAKE_CASED NUMBERSR эмулирование элементов в списке при повторении через itList <T> реализация для решения VB6 /VBADynamic для решения проблемы с рюкзакомRepository /Design Design Pattern Правильный способ повесить manBrainfuck на компилятор сборки x86

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

Итак, вот класс, который делает именно это, он запускает Action по определенному количеству раундов и вычисляет определенную статистику на нем.

Еще одна приятная особенность заключается в том, что она не сохраняет время выполнения при расчете статистики. Таким образом, вы можете буквально указать любое значение для rounds и должно работать. (Не проверено для rounds значений больше 10 000 000.)

Последняя версия на GitHub .

Это довольно просто. Два метода static на простом классе, который запускает эталон.

Хорошая вещь в этом классе заключается в том, что она включает в себя версию для Func<T>, которая также будет проверять выходную функцию . Это означает, что вы можете сравнивать и проверять свой код одновременно, чтобы убедиться, что ничего странного не происходит.

/// <summary>
/// Represents the result of a benchmarking session.
/// </summary>
public class BenchmarkResult
{
    /// <summary>
    /// The total number of rounds ran.
    /// </summary>
    public ulong RoundsRun { get; set; }

    /// <summary>
    /// The average time for all the rounds.
    /// </summary>
    public TimeSpan AverageTime { get; set; }

    /// <summary>
    /// The maximum time taken for a single round.
    /// </summary>
    public TimeSpan MaxTime { get; set; }

    /// <summary>
    /// The minimum time taken for a single round.
    /// </summary>
    public TimeSpan MinTime { get; set; }

    /// <summary>
    /// The variance (standard deviation) of all the rounds.
    /// </summary>
    public TimeSpan Variance { get; set; }

    /// <summary>
    /// The number of rounds that passed testing. (Always equivalent to <see cref="RoundsRun"/> for <see cref="Benchmark(ulong, Action)"/>.)
    /// </summary>
    public ulong RoundsPassed { get; set; }

    /// <summary>
    /// The total amount of time taken for all the benchmarks. (Does not include statistic calculation time, or result verification time.)
    /// </summary>
    /// <remarks>
    /// Depending on the number of rounds and time taken for each, this value may not be entirely representful of the actual result, and may have rounded over. It should be used with caution on long-running methods that are run for long amounts of time, though that likely won't be a problem as that would result in the programmer having to wait for it to run. (It would take around 29,247 years for it to wrap around.)
    /// </remarks>
    public TimeSpan TotalTime { get; set; }

    /// <summary>
    /// Runs a benchmark of a method.
    /// </summary>
    /// <param name="rounds">The number of rounds to run.</param>
    /// <param name="method">The code to run.</param>
    /// <returns>A <see cref="BenchmarkResult"/> representing the result of the session.</returns>
    public static BenchmarkResult Benchmark(ulong rounds, Action method)
    {
        var sw = new Stopwatch();

        double m2 = 0;
        double averageTicks = 0;
        double totalValues = 0;
        long maxTicks = 0;
        long minTicks = 0;
        long totalTime = 0;

        for (ulong i = 0; i < rounds; i++)
        {
            sw.Start();
            method.Invoke();
            sw.Stop();

            if (totalValues == 0)
            {
                maxTicks = sw.ElapsedTicks;
                minTicks = sw.ElapsedTicks;
            }

            totalValues++;

            maxTicks = Math.Max(sw.ElapsedTicks, maxTicks);
            minTicks = Math.Min(sw.ElapsedTicks, minTicks);

            // We need to store `delta` here as the `averageTicks` will change on the next calculation, and we need this previous `delta` for the calculation after it.
            double delta = sw.ElapsedTicks - averageTicks;
            averageTicks = averageTicks + delta / totalValues;
            m2 += delta * (sw.ElapsedTicks - averageTicks);

            totalTime += sw.ElapsedTicks;

            sw.Reset();
        }

        double variance = m2 / (totalValues - 1);

        return new BenchmarkResult
        {
            AverageTime = new TimeSpan(Convert.ToInt64(averageTicks)),
            MaxTime = new TimeSpan(maxTicks),
            MinTime = new TimeSpan(minTicks),
            RoundsPassed = rounds,
            RoundsRun = rounds,
            TotalTime = new TimeSpan(totalTime),
            Variance = new TimeSpan(Convert.ToInt64(variance))
        };
    }

    /// <summary>
    /// Runs a benchmark of a function and returns the results of the session.
    /// </summary>
    /// <typeparam name="T">The type of the output of the function.</typeparam>
    /// <param name="rounds">The number of rounds to run.</param>
    /// <param name="method">The code to run.</param>
    /// <param name="expectedResult">The expected result of the function. This will be compared to the actual result and used for <see cref="BenchmarkResult.RoundsPassed"/>. This uses the default <code>object.Equals(object)</code> method.</param>
    /// <returns>A <see cref="BenchmarkResult"/> representing the result of the session.</returns>
    public static BenchmarkResult Benchmark<T>(ulong rounds, Func<T> method, T expectedResult)
    {
        var sw = new Stopwatch();

        double m2 = 0;
        double averageTicks = 0;
        double totalValues = 0;
        long maxTicks = 0;
        long minTicks = 0;
        long totalTime = 0;
        ulong roundsPassed = 0;

        for (ulong i = 0; i < rounds; i++)
        {
            sw.Start();
            var result = method.Invoke();
            sw.Stop();

            if (expectedResult.Equals(result))
            {
                roundsPassed++;
            }

            if (totalValues == 0)
            {
                maxTicks = sw.ElapsedTicks;
                minTicks = sw.ElapsedTicks;
            }

            totalValues++;

            maxTicks = Math.Max(sw.ElapsedTicks, maxTicks);
            minTicks = Math.Min(sw.ElapsedTicks, minTicks);

            // We need to store `delta` here as the `averageTicks` will change on the next calculation, and we need this previous `delta` for the calculation after it.
            double delta = sw.ElapsedTicks - averageTicks;
            averageTicks = averageTicks + delta / totalValues;
            m2 += delta * (sw.ElapsedTicks - averageTicks);

            totalTime += sw.ElapsedTicks;

            sw.Reset();
        }

        double variance = m2 / (totalValues - 1);

        return new BenchmarkResult
        {
            AverageTime = new TimeSpan(Convert.ToInt64(averageTicks)),
            MaxTime = new TimeSpan(maxTicks),
            MinTime = new TimeSpan(minTicks),
            RoundsPassed = roundsPassed,
            RoundsRun = rounds,
            TotalTime = new TimeSpan(totalTime),
            Variance = new TimeSpan(Convert.ToInt64(variance))
        };
    }
}

Это буквально однострочный:

var result = BenchmarkResult.Benchmark((ulong)1e7, () => Thread.Sleep(0));
result = BenchmarkResult.Benchmark(10000000, () => true, true);

Это будет проверять метод Thread.Sleep(0) 10 000 000 раз, а метод return true - 10 000 000 раз, а при проверке всегда возвращает true.

0 голосов | спросил TyiloJalpesh VadgamaPhrancisMarcel BurkhardKerrek 27 Mayam13 2013, 03:27:25

3 ответа


38

Небольшие проблемы, прежде чем я попаду в большой:

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

  • Не используйте ulong, если вы не взаимодействуете с неуправляемым кодом. long имеет множество диапазонов. .NET использует подписанные величины даже для количеств, логически всегда положительных. Это облегчает выполнение таких вещей, как различие двух величин.


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

Так что сегодня просто быстрая заметка.

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

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

Теперь, можно сказать, хорошо, мы просто сделаем принудительную сборку (не забудьте дождаться готовых финализаторов!) после каждого теста и зарядите эту стоимость тестируемому коду. Это, безусловно, удалит из теста изменчивость , что хорошо. Но теперь мы сравниваем код в нереалистичной среде: среда, в которой GC полностью собран. Настоящие пользователи, запускающие ваш код, не будут работать в этом мире, поэтому теперь мы сделали меньше , уменьшив реалистичный . Эта изменчивость - это часть опыта пользователя кода , и вы удаляете это для целей тестирования.

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

ответил Eric Lippert 13 PMpWed, 13 Apr 2016 19:55:23 +030055Wednesday 2016, 19:55:23
38

Проблема с подобным подходом заключается в том, что вы не принимаете во внимание, что переключение процессоров /ядер занимает некоторое время, что приведет к подделке результатов. Этого можно избежать, установив ProcessorAffinity для использования только определенного процессора /ядра, например,

Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(2); // Use only the second core 

Чтобы обеспечить приоритет для этого ядра, нам нужно установить PriorityClass текущего процесса на High и приоритет текущего потока на Highest, например

Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
Thread.CurrentThread.Priority = ThreadPriority.Highest;  

Другая вещь, которая может подделать результаты, заключается в том, что вы не используете фазу прогрева, чтобы стабилизировать результаты. Это означает вызов желаемого Action /Function<T>, пока время, похоже, не будет таким же. Это отличается от компьютера к компьютеру, поэтому вы должны оценить, сколько времени потребуется для этого. Обычно я использую 5 секунд разминки, чтобы быть в безопасности.

Помните, что измерение правильное, если вы используете прогон без приложенного отладчика.

Хорошо читайте об этом (где большая часть этой информации)

ответил Heslacher 13 PMpWed, 13 Apr 2016 12:08:29 +030008Wednesday 2016, 12:08:29
12

У вас есть довольно слабые имена переменных. Например, что означает m2?


Мне не нравится тот факт, что результат порождает себя. Я бы предпочел иметь два отдельных класса: один для запуска теста и один для представления результата теста. Также вызов некоторого метода Benchmark.Run делает семантически более понятным, чем вызов BenchmarkResult.Benchmark.


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

public static BenchmarkResult Benchmark(ulong rounds, Action method)
{
    Benchmark(rounds, () => { method(); return true; }, true);
}

Он добавляет накладные расходы при возврате значения bool, но если метод method вы делаете какую-либо значимую работу, это не должно влиять на результат, учитывая тот факт, что контрольные показатели не так точно начинаются. Но это, вероятно, что-то, что вы можете захотеть ... benchmark.

Есть, безусловно, другие (возможно, менее красивые) способы удаления скопированного кода.

ответил Nikita B 14 AMpThu, 14 Apr 2016 10:44:05 +030044Thursday 2016, 10:44:05

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132