Pull to refresh

Comments 19

Я правильно понимаю, что настроить экспорт в CSV/этц можно только создавая ManualConfig, через атрибуты этого никак не добиться? И второй вопрос, можно ли группировать методы по BaseLine? Например я тестирую методы A1 A2 и B1 B2 в одном прогоне. Я могу сравнить A1 с A2, а B1 с B2, попарно? Или Baseline выбирается один на весь прогон?
  1. На текущий момент экспорт в csv делается всегда, а через ManualConfig можно его выключить.
  2. Сейчас Baseline выбирается на группу параметров. Т.е. если у меня есть [Params(1,2,3)] int X, то на каждое значение X будет собственный Baseline. Если вы хотите сравнивать A1 с A2, а B1 с B2 попарно, то нужно делать два отдельных класса. Группировку методов сделать не так сложно, но сложно сделать так, чтобы для общего случая результаты отображались бы так, чтобы всем было понятно, к чему какой Baseline-относится (если у вас есть хорошие идеи, то я их с радостью послушаю).
    Но если очень хочется, то всегда можно прописать собственный IColumn по аналогии с BaselineDiffColumn и добавить в конфиг (кода нужно совсем немного, зато вы сможете заточить колонку под собственные нужды).
Ну мой юзкейс на данный момент такой: у меня есть мой класс, который повторяет функционал другого, АПИ одинаковый. Я хочу сравнить их с точки зрения производительности, допустим 10 моих методов и 10 методов этого класса. Соответственно каждый сравнить со своим аналогом и сделать какие-то выводы. На данный момент формируется все в одну стенку, и без baseline немного неудобно.

На данный момент я еще думаю, есть ли смысл делать делегат возможным вариантом Params'ов? Ну например, анализируем метод Max, Соответственно пишем

[Params(Enumerable.Max, CustomClass.Max)]
public Func<int[], int> Max {get;set;}

Либо это блажь и через методы все прекрасно делается?.. Честно, сейчас не могу привести конкретного примера, на котором именно атрибуты лучше, но читаемость на мой взгляд не страдает. А, вспомнил. Очень удобно, когда функция показана в отдельном стобце, а не кодируется в названии метода LinqMax, CustomMax и т.п.
А есть возможность сделать так, чтобы все методы были в интерфейсе, который реализуется в каждом из классов? Если да, то проще всего было бы завести параметр, который бы определял имплементацию. Ну, и в каждом бенчмарк-методе использовать этот параметр, чтобы использовался бы инстанс нужного класса. И можно было бы легко написать свой BaselineColumn, который сравнивает первую имплементацию со второй. Если это не нанобенчмарки (на метод уходит хотя бы 50ns), то накладными расходами на выбор имплементации вполне можно пренебречь.

Очень удобно, когда функция показана в отдельном стобце, а не кодируется в названии метода LinqMax, CustomMax и т.п.

А можете привести пример того, как вы это себе представляете?
Ну например так:

Max     | Linq   | 0.456347
Max     | Custom | 1
Average | Linq   | 0.784584
Average | Custom | 1

А есть возможность сделать так, чтобы все методы были в интерфейсе, который реализуется в каждом из классов?

Ну в моем случае нет, т.к. тестируются статические extension методы. Нет, не нанобенчмарки, но ООП тут использовать, увы, нельзя.

Я планирую написать статью на эту тему (со скриншотами вашей классной библиотечки :) ), там подробнее обо всем расскажу. Но в двух словах вот так.
Ну, выдирать имя функции из тела — это достаточно частная задача, не думаю, что имеет смысл включать её в ядро библиотеки. В вашем случае я бы предложил использовать TagColumn для формирования дополнительных колонок из названий методов, см. IntroTags. Свой Baseline будет писаться очень просто: если нам дали Custom-класс, то возвращаем в новой чудо-колонке 1, а если Linq, то находим бенчмарк с такой же функцией в Custom-случае и считаем отношение.

Если по мере подготовки статьи будут возникать какие-то дополнительные вопросы/замечания/предложения, то не стесняйтесь обращаться. В BenchmarkDotNet мне хочется сделать такой API, который можно действительно легко адаптировать под любые кастомные задачи.
С удовольствием оставлю фидбек, 2 issue уже завел на гитхабе :) По мере возникновения вопросов конечно можно вырабатывать какую-то общую точку зрения. Статья пока в самом зачаточном состоянии, я только-только закончил писать тесты и определил некоторые необходимые константы, собственно с помощью бенчмарков я и пытаюсь это определить, например, начиная с какого размера коллекции имеет смысл распараллеливать алгоритм. Т.к. задача зависит от нескольких факторов, например от сложности делегата, то для определения оптимальной константы требуется работа с делегатами. В связи с чем на данный момент используется подход, что есть просто строковое представление функций и словарь, который в нужный момент времени подставляет нужную функцию. Но если вы считаете, что это достаточно частная задача, что ж, не буду спорить, не я разработчик :) Я не знаю всех нюансов.
По поводу Issues. #97 уже сделан в develop ветке, #96 скоро сделаю. Постараюсь выложить новую версию в NuGet на этих выходных. Помимо прочего, у нас там идёт работа над поддержкой DNX и CoreCLR, что занимает много времени.
По поводу вашего случая: если у вас получится придумать удобный API который бы покрывал ваш случай и не мешал бы работать обычным бенчмарком, то я с удовольствием включу его в библиотеку. Но лично я пока плохо представляю, как такой функционал следовало бы реализовать. Было бы чудно, если бы вы накидали в виде кода то, как вы всё это видите (+ желаемый вид summary-таблички).
Ну я себе это так представляю:

var summary = BenchmarkRunner.Run<BaseLineClass, Comp1Class, Comp2Class>();

В результате ищутся все методы помеченные как [Benchmark], в остальных классах ищутся соответствующие им (по названию, например) методы, после чего замеряются, причем вызов первого класса является эталонным, а остальные замеряются в пропорции к нему, как у вас уже сделано. Пример вывода выше: первичны функции, вторичны классы. То есть нужно наглядно сравнить, что вот ага, этот метод быстрее чем аналог в 10 раз, а вот этот медленнее в полтора раза, его надо пофиксить.

Еще есть мысль сортировать по выигрышу. То есть например в самом верху метод, который в 100 раз выиграл у дефолтной реализации. А в самом низу метод, который в 10000 раз проиграл. Ну и дальше тиражировать с помощью Params всё вот это дело, как сейчас собственно и работает.

Самый простой вариант: сравнивается 2 класса. Менее типичный, но все же реальный: сравниваются 3-4 класса и выбирается наилучший. Ну и для извращенцев можно сделать params Type[], чтобы аналогично сколько угодно классов можно было сравнивать. В результате первые 3 это просто удобная обертка для последнего метода, который должен учесть, что есть N реализаций какого-то метода, причем не обязательно с одинаковой сигнатурой: например я сравниваю стандартный метод, который принимает Func<T, TResult>, а в моей реализации используется Expression<Func<T, TResult>>, соответственно нужно ориентироваться только на имя. Ну или можно еще атрибут навесить, с названием метода, которое должно быть уникальным в пределах класса и соответствовать именам тестируемых функций в соревнующихся классах.
Ок, я подумаю, что с этим можно сделать.
В комментарии выше дан разумный совет.
У вас есть Method1, Method2… Method10 методы, которые реализуются классами OldClass, NewClass. Нужно сравнить OldClass.Method1 с NewClass.Method1, OldClass.Method2 с NewClass.Method2, etc.

По факту, нужны 10 бенчмарков (по бенчмарку для каждого из методов). Вы же не хотите сравнивать быстродействие OldClass.Method3 с NewClass.Method4, например. Так что вам нужен код вроде

    public class Benchmark_Method1
    {
        [Benchmark(Description="OldClass_Method1", BaseLine = true)]
        public void OldClass_Method1()
        {
            new OldClass().Method1();
        }

        [Benchmark(Description="NewClass_Method1")]
        public void NewClass_Method1()
        {
            new NewClass().Method1();
        }
    }

    // Тут еще 9 похожих классов
    [TestClass]
    public class Benchmarks
    {
        [TestMethod]
        public void TestMethod1()
        {            
            BenchmarkRunner.Run<Benchmark_Method1>();
        }

        [TestMethod]
        public void TestMethod2()
        {            
            BenchmarkRunner.Run<Benchmark_Method2>();
        }

        // Тут еще 8 похожих тестов
    }
Это копипаста. А любая копипаста намекает на то, что программист что-то делает неправильно. DRY придумали давно. Соответственно проблема может быть только в двух вещах: либо неправильное АПИ у библиотеки, либо криворукий Я не может правильно воспользоваться данным инструментом. Собственное, я бы хотел возможность сравнить два массива методов попарно. Я не думаю, что задача написать более быстрый аналог какого-то класса и обоснование его "лучшести" по результатам бенчмарка такая редкая.
Ваши аргументы понятны, но есть пара моментов.

  1. Каждый модульный тест должен тестировать одну проблему (в идеале). В нашем случае — проводить сравнение одной пары методов.
  2. Классы с "замеряющими" методами выглядят схоже, но начинка у них может быть разная. Где-то тестируются методы с аргументами, где-то проводится инициация. В одной из веток комментариев Вы упоминали про желание определить, при каких параметрах распараллеленный метод работает быстрее обычного. То есть, настраивается поле с Params атрибутом. Для других методов этот Params будет лишним, и тогда вынос этих методов в отдельный класс становится необходимостью.

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

        [TestMethod]
        public void TestMethod1()
        {            
            Summary result1 = BenchmarkRunner.Run<Benchmark_Method1>();
            Summary result2 = BenchmarkRunner.Run<Benchmark_Method2>();
            ...

            Console.WriteLine(ExtractInfoFromSummary(result1));
            Console.WriteLine(ExtractInfoFromSummary(result2));
            ...            
        }

Думаю, Вы сможете отрефакторить пример парочкой foreach, избавившись от копипасты.
Если я правильно понимаю, то библиотека собирает из оригинальных бенчмарков другие бинарные артефакты, которые используются непосредственно в рантайме для оценки времени исполнения. А как дебажить это дело, если, брейкпойнты привязаны к оригинальному коду и ничего их волшебно не отображает на правильные места в новых бинарниках?

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

Этот бенчмарк какой то жутчайший панос. Во первых миллион спама в консоли что не видно начала. Во вторых каким то хреном лезет к исходникам и там падает с исключениями. В конце результатов я не увидел, консоль упала. Это не тест а гавно.
imgur.com/a/70g5m

Зачем она ищет сорцы кто ее просил?

namespace LoadTest
{
    class Program
    {
        static void Main(string[] args)
        {
            TestMethod1();

            //Console.WriteLine("Hello World!");
            Console.ReadLine();
        }

        
        public static void TestMethod1()
        {
            var result1 = BenchmarkRunner.Run<TheEasiestBenchmark>();
        

            Console.WriteLine(result1);
        }
    }

    public class TheEasiestBenchmark
    {
        [Benchmark(Description = "Summ100")]
        public int Test100()
        {
            return Enumerable.Range(1, 100).Sum();
        }

        [Benchmark(Description = "Summ200")]
        public int Test200()
        {
            return Enumerable.Range(1, 200).Sum();
        }
    }
}


Простой тест и куча геморроя. Программа не должна делать то, что ее не просили. Где ты видишь атрибут или опцию лезть в сорцы? Почему ее нет? Без сорцев тест вобще не запустился, скопировал на рабочий стол. Мне что сорцы надо таскать, что бы эти тесты запускать? Это пример плохой архитектуры. Опять же создает три файла csv, html и еще что то. Опять же зачем и кто просил это делать? И почему в каталог BenchmarkDotNet.Artifacts? Что еще делают эти тесты а мы этого не знаем? В итоге вместо понятно теста, под предлогом вы все идиоты, а я самый умный, мы имеем черный ящик, который не понятно что делает и зачем, при этом результаты таких тестов не вызывают никакого доверия.
Sign up to leave a comment.

Articles

Change theme settings