День 1358. #Testing #Benchmark
Руководство по Бенчмарку в .NET. Начало
В этой серии постов рассмотрим, как создать проект .NET для тестирования производительности.
Начнём с чистого листа и создадим проект. Мы протестируем два метода, которые разными способами объединяют строки. Для этого будем использовать пакет BenchmarkDotNet.
Если вы, как я, не можете запомнить структуру проекта с бенчмарком и постоянно её гуглите, можно просто установить шаблоны:
Шаблон создаёт 2 класса:
- Benchmarks, в который добавляет 2 метода для тестовых сценариев: Scenario1 и Scenario2, помеченные атрибутом [Benchmark],
- Program, который собственно запускает бенчмарк.
Назовём методы удобными именами и добавим простые методы объединения строк: через конкатенацию и через StringBuilder.
Бенчмарк выдаст подробный отчёт. В начале выдаётся информация о системе, в которой проходили тесты, а затем собственно результаты с разными статистическими подробностями. Мы обратим внимание на колонку Mean (Среднее):
Продолжение следует…
Источник: https://blog.nimblepros.com/blogs/benchmarking-in-dotnet/
Руководство по Бенчмарку в .NET. Начало
В этой серии постов рассмотрим, как создать проект .NET для тестирования производительности.
Начнём с чистого листа и создадим проект. Мы протестируем два метода, которые разными способами объединяют строки. Для этого будем использовать пакет BenchmarkDotNet.
Если вы, как я, не можете запомнить структуру проекта с бенчмарком и постоянно её гуглите, можно просто установить шаблоны:
dotnet new install BenchmarkDotNet.TemplatesТеперь создадим проект, используя шаблон:
dotnet new benchmark --console-app -f net6.0 -o StringBenchmarksЗдесь мы используем несколько флагов:
--console-app – консольное приложение,-f net6.0 – целевой фреймворк .NET 6.0,-o StringBenchmarks – название проекта и папки для него.Шаблон создаёт 2 класса:
- Benchmarks, в который добавляет 2 метода для тестовых сценариев: Scenario1 и Scenario2, помеченные атрибутом [Benchmark],
- Program, который собственно запускает бенчмарк.
Назовём методы удобными именами и добавим простые методы объединения строк: через конкатенацию и через StringBuilder.
namespace StringBenchmarks;Да, метод со StringBuilder выведет лишнюю запятую. Мы к этому ещё вернёмся. Запускаем проект:
public class Benchmarks
{
[Benchmark]
public string StringJoin()
{
return string.Join(", ",
Enumerable.Range(0, 10)
.Select(i => i.ToString()));
}
[Benchmark]
public string StringBuilder()
{
var sb = new StringBuilder();
for (int i = 0; i < 10; i++)
{
sb.Append(i);
sb.Append(", ");
}
return sb.ToString();
}
}
dotnet run -c ReleaseЗдесь стоит отметить, что должна использоваться конфигурация Release, чтобы максимально оптимизировать сборку. Кроме того, если вы хотите, чтобы тесты были максимально точными, закройте все работающие приложения и запустите проект из терминала.
Бенчмарк выдаст подробный отчёт. В начале выдаётся информация о системе, в которой проходили тесты, а затем собственно результаты с разными статистическими подробностями. Мы обратим внимание на колонку Mean (Среднее):
| Method | Mean |Как видите, метод, использующий StringBuilder, работает заметно быстрее.
|-------------- |---------:|
| StringJoin | 69.71 ns |
| StringBuilder | 41.19 ns |
Продолжение следует…
Источник: https://blog.nimblepros.com/blogs/benchmarking-in-dotnet/
👍13
День 1359. #Testing #Benchmark
Руководство по Бенчмарку в .NET. Продолжение
Начало
Мы можем выводить дополнительную информацию в отчёте с помощью атрибутов.
Память
Наверное, самым популярным является
Базовый случай
Иногда полезно обозначить базовый метод, относительно которого мы будем считать, насколько быстрее (или медленнее) работают остальные. Это можно сделать, добавив в атрибут
Параметры
Можно посмотреть, как себя ведёт метод в зависимости от объёма данных. В этом поможет атрибут
Среда исполнения
Мы можем сравнить быстродействие кода в разных средах исполнения. Например, .NET 6.0 с .NET 7.0. Для этого используется атрибут
Собираем всё вместе:
Результаты приведены на картинке ниже. Из результатов, например, заметно, что метод, использующий StringBuilder, с каждой новой версией .NET работает всё быстрее.
Не обязательно просматривать результаты в консоли. BenchmarkDotNet выводит результаты в папку BenchmarkDotNet.Artifacts. Там будут файлы отчетов в форматах html, csv и markdown. Это может быть очень полезно для добавления в PR или комментарий к релизу на Github или других подобных платформах.
Окончание следует…
Источник: https://blog.nimblepros.com/blogs/benchmarking-in-dotnet/
Руководство по Бенчмарку в .NET. Продолжение
Начало
Мы можем выводить дополнительную информацию в отчёте с помощью атрибутов.
Память
Наверное, самым популярным является
MemoryDiagnoser, позволяющий посмотреть информацию о потребляемой памяти и количестве сборок мусора.Базовый случай
Иногда полезно обозначить базовый метод, относительно которого мы будем считать, насколько быстрее (или медленнее) работают остальные. Это можно сделать, добавив в атрибут
Benchmark параметр Baseline=true.Параметры
Можно посмотреть, как себя ведёт метод в зависимости от объёма данных. В этом поможет атрибут
Params(…), в который надо передать массив размеров входных данных.Среда исполнения
Мы можем сравнить быстродействие кода в разных средах исполнения. Например, .NET 6.0 с .NET 7.0. Для этого используется атрибут
SimpleJob(RuntimeMoniker.…). Убедитесь, что у вас установлена версия BetnchmarkDotNet 0.13 или выше, чтобы тестировать в средах .NET 5.0 и выше. Также убедитесь, что все эти среды исполнения установлены на вашей машине.Собираем всё вместе:
namespace StringBenchmarks {
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net50)]
[SimpleJob(RuntimeMoniker.Net60, baseline: true)]
[SimpleJob(RuntimeMoniker.Net70)]
public class Benchmarks
{
[Params(5, 50, 500)]
public int N { get; set; }
[Benchmark(Baseline = true)]
public string StringJoin()
{
return string.Join(", ",
Enumerable.Range(0, N)
.Select(i => i.ToString()));
}
[Benchmark]
public string StringBuilder()
{
var sb = new StringBuilder();
for (int i = 0; i < N; i++)
{
sb.Append(i);
sb.Append(", ");
}
return sb.ToString();
}
}
}
Мы добавили параметр N, которому бенчмарк задаст значения 5, 50 и 500 соответственно в разных тестах. Также мы запустим тесты в 3х средах исполнения: .NET 5.0, .NET 6.0 (базовая среда) и .NET 7.0. Кроме того, добавлена диагностика памяти и за базовый случай взят метод StringJoin.Результаты приведены на картинке ниже. Из результатов, например, заметно, что метод, использующий StringBuilder, с каждой новой версией .NET работает всё быстрее.
Не обязательно просматривать результаты в консоли. BenchmarkDotNet выводит результаты в папку BenchmarkDotNet.Artifacts. Там будут файлы отчетов в форматах html, csv и markdown. Это может быть очень полезно для добавления в PR или комментарий к релизу на Github или других подобных платформах.
Окончание следует…
Источник: https://blog.nimblepros.com/blogs/benchmarking-in-dotnet/
👍13
День 1360. #Testing #Benchmark
Руководство по Бенчмарку в .NET. Окончание
Начало
Продолжение
Настройка и очистка
Иногда нужно написать какую-то логику, которая должна выполняться до или после бенчмарка, но мы не хотим, чтобы она участвовала в бенчмарке.
- Метод, помеченный атрибутом
- Метод, помеченный атрибутом
- Метод, помеченный атрибутом
- Метод, отмеченный атрибутом
Валидация
Итак, бенчмарк позволяет сравнить между собой производительность методов, которые, по идее, должны делать одно и то же. Однако, если внимательно присмотреться к коду из первой части, можно заметить, что это не так. Метод StringBuilder() выводит лишнюю запятую в конце, в отличие от метода StringJoin(). Эта конкретная ошибка вряд ли сильно повлияла на результаты бенчмарка, но нам может и не повезти с этим в следующий раз.
Хотелось бы иметь какой-то автоматизированный тест, подтверждающий, что результат работы методов одинаковый.
BenchmarkDotNet умеет и это. Всё, что нам нужно сделать, это добавить
// Validating benchmarks (Проверка бенчмарков):
Inconsistent benchmark return values in Benchmarks (Несогласованные возвращаемые значения в бенчмарках): StringJoin: 0, 1, 2, 3, 4, StringBuilder: 0, 1, 2, 3, 4,
* Здесь значения кажутся одинаковыми, но на самом деле это из-за того, что BenchmarkDotNet ставит запятую в сообщении об ошибке после результата первого метода. На самом деле это следует читать как:
StringJoin: "0, 1, 2, 3, 4", StringBuilder: "0, 1, 2, 3, 4,"
И бенчмарки не выполнятся. Нам нужно исправить ошибку и убедиться, что результаты обоих методов одинаковы.
Также существует
Источники:
- https://blog.nimblepros.com/blogs/validating-benchmarks/
- https://benchmarkdotnet.org/articles/features/setup-and-cleanup.html
- https://benchmarkdotnet.org/articles/configs/validators.html
Руководство по Бенчмарку в .NET. Окончание
Начало
Продолжение
Настройка и очистка
Иногда нужно написать какую-то логику, которая должна выполняться до или после бенчмарка, но мы не хотим, чтобы она участвовала в бенчмарке.
- Метод, помеченный атрибутом
[GlobalSetup], будет выполняться только один раз для тестируемого метода после инициализации параметров бенчмарка и до всех вызовов тестового метода.- Метод, помеченный атрибутом
[GlobalCleanup], будет выполняться только один раз для тестируемого метода после всех вызовов тестового метода.- Метод, помеченный атрибутом
[IterationSetup], будет выполняться ровно один раз перед каждым вызовом теста. Не рекомендуется использовать его в микробенчмарках, так как это может испортить результаты. Однако, он может быть полезен, если тест занимает не менее 100 мс, и вы хотите подготовить некоторые данные перед каждым вызовом.- Метод, отмеченный атрибутом
[IterationCleanup], будет выполняться ровно один раз после каждого вызова. Этот атрибут также не рекомендуется использовать в микробенчмарках.Валидация
Итак, бенчмарк позволяет сравнить между собой производительность методов, которые, по идее, должны делать одно и то же. Однако, если внимательно присмотреться к коду из первой части, можно заметить, что это не так. Метод StringBuilder() выводит лишнюю запятую в конце, в отличие от метода StringJoin(). Эта конкретная ошибка вряд ли сильно повлияла на результаты бенчмарка, но нам может и не повезти с этим в следующий раз.
Хотелось бы иметь какой-то автоматизированный тест, подтверждающий, что результат работы методов одинаковый.
BenchmarkDotNet умеет и это. Всё, что нам нужно сделать, это добавить
ReturnValueValidator в наш тестовый класс, и все готово.[ReturnValueValidator(failOnError: true)]Теперь при попытке запуска нашего бенчмарка, если методы не возвращают одинаковый результат, мы получим ошибку:
public class Benchmarks
{
// остальной код скрыт для краткости
// см. предыдущий пост
}
// Validating benchmarks (Проверка бенчмарков):
Inconsistent benchmark return values in Benchmarks (Несогласованные возвращаемые значения в бенчмарках): StringJoin: 0, 1, 2, 3, 4, StringBuilder: 0, 1, 2, 3, 4,
* Здесь значения кажутся одинаковыми, но на самом деле это из-за того, что BenchmarkDotNet ставит запятую в сообщении об ошибке после результата первого метода. На самом деле это следует читать как:
StringJoin: "0, 1, 2, 3, 4", StringBuilder: "0, 1, 2, 3, 4,"
И бенчмарки не выполнятся. Нам нужно исправить ошибку и убедиться, что результаты обоих методов одинаковы.
Также существует
BaselineValidator, который проверяет, что параметр Baseline=true атрибута Benchmark добавлен только в одном методе. Этот валидатор обязательный.JitOptimizationsValidator проверяет, все ли зависимости проекта оптимизированы. По умолчанию отключен.ExecutionValidator проверяет, могут ли исполниться все бенчмарки, исполняя все методы по одному разу перед запуском тестов. Если в каком-то из методов возникает ошибка, бенчмарки не запускаются. По умолчанию отключен.Источники:
- https://blog.nimblepros.com/blogs/validating-benchmarks/
- https://benchmarkdotnet.org/articles/features/setup-and-cleanup.html
- https://benchmarkdotnet.org/articles/configs/validators.html
👍16