Информация об изменениях

Сообщение [CodeJam] PerformaceTests, pre-alpha от 11.04.2016 21:11

Изменено 12.04.2016 20:18 Sinix

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

Поехали

Что это такое


Набор хелперов для competition performance tests, работающий поверх единственного более-менее правильного .net-фреймворка для бенчмарков — BenchmarkDotNet. В CodeJam лежит в проекте CodeJam.Main-Tests.Performance.

Говоря русским языком, эта штука позволяет написать набор реализаций-кандидатов, дополнить их образцом (baseline test), оформить в виде юнит-теста, и дальше CI-сервер за вас сам следит, что все реализации укладываются в заданные лимиты по производительности. Лимиты записываются в виде коэффициентов — производительность относительно baseline.
Абсолютные значения тут неприменимы по очевидным причинам: тесты будут выполняться на разном железе/в разном окружении и абсолютное время может легко скакать в разы.


Главная и уникальная киллфича проекта: нет необходимости самому размечать лимиты по перфомансу для каждого из тестов, это делается автоматически.

Чтоб было понятно о чём речь: простейший тест, который гарантирует, что sum += i выполняется примерно в полтора раза быстрее, чем sum += i + 1 выглядит
  вот так:
    public class ProofsSensitivityBenchmark
    {
        [Test]
        public void BenchmarkSensitivity() => CompetitionBenchmarkRunner.Run(this, AssemblyWideConfig.RunConfig);

        public const int Count = 100 * 1000;

        // baseline
        [Benchmark(Baseline = true)]
        public int Test00Baseline()
        {
            var sum = 0;
            for (var i = 0; i < Count; i++)
            {
                sum += i;
            }
            return sum;
        }

        // не быстрее, чем 1.55 от baseline
        // не медленнее, чем 1.65 от baseline
        [CompetitionBenchmark(1.55, 1.65)]
        public int Test01PlusOne()
        {
            var sum = 0;
            for (var i = 0; i < Count; i++)
            {
                sum += i + 1;
            }
            return sum;
        }
    }

Минимальный/максимальный коэффициенты в [CompetitionBenchmark(1.55, 1.65)] заполнены автоматически. После того, как режим автозаполнения отключен, тест упадёт, если Test01PlusOne будет выполняться быстрее/медленнее заданных значений.


Зачем это нужно

Во-первых, чтобы принимать обоснованные решения о выборе конкретной реализации и чтобы узнать, что текущая реализация перестала быть оптимальной при смене компилятора/фреймворка.
Пример — см папку Arithmetic с перфтестами для Operators<T>.

Во-вторых, для регрессионных юнит-тестов и для документирования perf-контракта.


В каком оно состоянии

Pre-alpha. Проблема в следующем: BenchmarkDotNet не затачивался под запуск в виде юнит-тестов, всю доработку напильником приходится делать самому.

К сожалению, не всё можно обойти самостоятельно и часть изменений надо продавить в виде pull requests в сам BenchmarkDotNet.
Сюрпрайз №2: похоже, очень мало кто занимается перфтестами, поэтому помимо "что надо поменять" нужно ещё обосновать "зачем это надо" даже в очевидных (мне ) моментах.
Это ещё больше замедляет дело, так что точных сроков когда я назвать пока не могу.

Проблемные моменты:
1. В текущей реализации каждый метод-кандидат собирается как отдельный бинарник и запускается в собственном процессе. В лучшем случае каждый тест занимает 5-10 секунд, бонусом идёт 2..20 мб мусора в папке bin. Это ключевой затык, тикет с проверенно рабочим решением заведён, жду ответа от команды Bench.NET.

2. Нет возможности задать лимиты по аллокациям/сборкам мусора. Точнее, она есть, но ждёт реализации п.1.


Как использовать:

На сегодня — только внутри самого CodeJam. Я намеренно не выпускаю отдельный пакет, т.к. в процессе догфудинга время от времени удаётся заметно облегчить написание/использование юнит-тестов. В частности, с вероятностью в 100% будут поправлены имена классов/методов в самих перфтестах, текущий вариант неудачный.

Если есть желание добавить свой перфест, на примере кода выше:

1. Написать метод-образец, пометить как [Benchmark(Baseline = true)]

2. Написать методы-кандидаты, пометить их как [CompetitionBenchmark]. В параметрах конструктора атрибута можно указать лимиты по времени в виде мин/макс коэффициентов относительно Baseline-метода.

3. Добавить запуск бенчмарка:
        [Test]
        public void TestName() => CompetitionBenchmarkRunner.Run(this, AssemblyWideConfig.RunConfig);
поддерживается любой из юнит-тест-фреймворков. Главное, чтоб он распознавал исключения как ошибки теста и перенаправлял вывод на консоль в output теста.

4. Если лень расставлять коэффициенты самому:

4.1. DISCLAIMER: временное решение. Возможно, будет переделано как отдельный BuildConfiguration.

В файле AssemblyWideConfig.cs в строчке
        /// <summary>
        /// OPTIONAL: Set this to true to enable auto-annotation of benchmark methods
        /// </summary>
        public static readonly bool AnnotateOnRun = false; // = true;

заменить false на true.

Есть варианты, как этот момент оформить поизящнее — велкам!

4.2. Запустить тест вручную через VS test explorer. Раннер решарпера (по крайней мере в EAP) пока не поддерживается.
* В режиме автоаннотации тест выполняется в несколько проходов (до 10), пока не начнёт укладываться в свежесобранные лимиты. Т.е. если обычных десяти секунд тест выполняется полторы минуты — это нормально.

После завершения работы теста значения min/max в [CompetitionBenchmark] будут заполнены актуальными лимитами.

5. После того, как лимиты расставлены, запустить тест. Если он зелёный — вы угадали с лимитами. Если он красный — или лимиты слишком жёсткие, или реализация одного из методов поменялась и вы нашли баг
Подробнее — см сообщения теста и таблицу с результатами в output.

Чтобы отключить режим автоаннотации замените AnnotateOnRun обратно в false. Постарайтесь не закоммитить код с AnnotateOnRun = true
Да, я знаю, что это идиотский, неудобный и принципиально неправильный способ. Есть предложения — велкам!

P.S. Будет обновляться по мере появления вопросов/прогресса/наличия свободного времени.
Отпишусь пожалуй, чтобы не забыть по горячим следам и потом проще было документацию писать.

Поехали

Что это такое


Набор хелперов для competition performance tests, работающий поверх единственного более-менее правильного .net-фреймворка для бенчмарков — BenchmarkDotNet. В CodeJam лежит в проекте CodeJam.Main-Tests.Performance.

Говоря русским языком, эта штука позволяет написать набор реализаций-кандидатов, дополнить их образцом (baseline test), оформить в виде юнит-теста, и дальше CI-сервер за вас сам проверяет, что все реализации укладываются в заданные лимиты по производительности. Лимиты записываются в виде коэффициентов — производительность относительно baseline.
Абсолютные значения тут неприменимы по очевидным причинам: тесты будут выполняться на разном железе/в разном окружении и абсолютное время может легко различаться в разы.


Главная и уникальная киллфича проекта: нет необходимости самому размечать лимиты по перфомансу для каждого из тестов, это делается автоматически.

Чтоб было понятно о чём речь: простейший тест, который гарантирует, что sum += i выполняется примерно в полтора раза быстрее, чем sum += i + 1 выглядит
  вот так:
    public class ProofsSensitivityBenchmark
    {
        [Test]
        public void BenchmarkSensitivity() => CompetitionBenchmarkRunner.Run(this, AssemblyWideConfig.RunConfig);

        public const int Count = 100 * 1000;

        // baseline
        [CompetitionBaseline)]
        public int Test00Baseline()
        {
            var sum = 0;
            for (var i = 0; i < Count; i++)
            {
                sum += i;
            }
            return sum;
        }

        // не быстрее, чем 1.55 от baseline
        // не медленнее, чем 1.65 от baseline
        [CompetitionBenchmark(1.55, 1.65)]
        public int Test01PlusOne()
        {
            var sum = 0;
            for (var i = 0; i < Count; i++)
            {
                sum += i + 1;
            }
            return sum;
        }
    }

Минимальный/максимальный коэффициенты в [CompetitionBenchmark(1.55, 1.65)] заполнены автоматически. После того, как режим автозаполнения отключен, тест упадёт, если Test01PlusOne будет выполняться быстрее/медленнее заданных значений.


Зачем это нужно

Во-первых, чтобы принимать обоснованные решения о выборе конкретной реализации и чтобы узнать, что текущая реализация перестала быть оптимальной при смене компилятора/фреймворка.
Пример — см папку Arithmetic с перфтестами для Operators<T>.

Во-вторых, для регрессионных юнит-тестов и для документирования perf-контракта.


В каком оно состоянии

Pre-alpha. Проблема в следующем: BenchmarkDotNet не затачивался под запуск в виде юнит-тестов, всю доработку напильником приходится делать самому.

К сожалению, не всё можно обойти самостоятельно и часть изменений надо продавить в виде pull requests в сам BenchmarkDotNet.
Сюрпрайз №2: похоже, очень мало кто занимается перфтестами, поэтому помимо "что надо поменять" нужно ещё обосновать "зачем это надо" даже в очевидных (мне ) моментах.
Это ещё больше замедляет дело, так что точных сроков когда я назвать пока не могу.

Проблемные моменты:
1. В текущей реализации каждый метод-кандидат собирается как отдельный бинарник и запускается в собственном процессе. В лучшем случае каждый тест занимает 5-10 секунд, бонусом идёт 2..20 мб мусора в папке bin. Это ключевой затык, тикет с проверенно рабочим решением заведён, жду ответа от команды Bench.NET.

2. Нет возможности задать лимиты по аллокациям/сборкам мусора. Точнее, она есть, но ждёт реализации п.1.


Как использовать:

На сегодня — только внутри самого CodeJam. Я намеренно не выпускаю отдельный пакет, т.к. в процессе догфудинга время от времени удаётся заметно облегчить написание/использование юнит-тестов. В частности, с вероятностью в 100% будут поправлены имена классов/методов в самих перфтестах, текущий вариант неудачный.

Если есть желание добавить свой перфест, на примере кода выше:

1. Написать метод-образец, пометить как [CompetitionBaseline]

2. Написать методы-кандидаты, пометить их как [CompetitionBenchmark]. В параметрах конструктора атрибута можно указать лимиты по времени в виде мин/макс коэффициентов относительно Baseline-метода.

3. Добавить запуск бенчмарка:
        [Test]
        public void TestName() => CompetitionBenchmarkRunner.Run(this, AssemblyWideConfig.RunConfig);
поддерживается любой из юнит-тест-фреймворков. Главное, чтоб он распознавал исключения как ошибки теста и перенаправлял вывод на консоль в output теста.

4. Если лень расставлять коэффициенты самому:

4.1. в App.config ставим value="true" в строчке
        <add key="AssemblyWideConfig.AnnotateOnRun" value="false" />

Чтобы случайно не закоммитить изменённый конфиг, рекомендую пометить его как skip-worktree (в tortoisegit: check for modifications &mdash; Skip worktree в контекстном меню).

4.2. Запустить тест вручную через VS test explorer. Раннер решарпера (по крайней мере в EAP) пока не поддерживается.
* В режиме автоаннотации тест выполняется в несколько проходов (до 10), пока не начнёт укладываться в свежесобранные лимиты. Т.е. если обычных десяти секунд тест выполняется полторы минуты — это нормально.

После завершения работы теста значения min/max в [CompetitionBenchmark] будут заполнены актуальными лимитами.

5. После того, как лимиты расставлены, запустить тест. Если он зелёный — вы угадали с лимитами. Если он красный — или лимиты слишком жёсткие, или реализация одного из методов поменялась и вы нашли баг
Подробнее — см сообщения теста и таблицу с результатами в output.

6. Для динамически генерируемых бенчмарков лимиты можно задавать в виде xml-файла. Пример см в NumOperatorsPerfTest.generated.cs. Для этого надо:
6.1. Добавить xml-файл рядом с файлом с кодом бенчмарка, build action — embedded resource. Имя файла должно различаться только расширением (.xml вместо .cs). Содержимое файла:
<?xml version="1.0" encoding="utf-8"?>
<CompetitionBenchmarks>
</CompetitionBenchmarks>

6.2. Пометить класс с бенчмарками атрибутом
    [CompetitionMetadata("CodeJam.Arithmetic.NumOperatorsPerfTest.generated.xml")] // имя ресурса
    public class NumOperatorsPerfTest

Параметр атрибута — имя ресурса с xml-файлом.
6.3. В коде не указывать лимиты в виде параметров [CompetitionBenchmark] — они будут игнорироваться.
6.4. Запустить перфтест в режиме авто-аннотирования.

Вопросы / предложения? Велкам

P.S. Будет обновляться по мере появления вопросов/прогресса/наличия свободного времени.