.NET Разработчик
6.54K subscribers
442 photos
3 videos
14 files
2.12K links
Дневник сертифицированного .NET разработчика. Заметки, советы, новости из мира .NET и C#.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 1358. #Testing #Benchmark
Руководство по Бенчмарку в .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;

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();
}
}
Да, метод со StringBuilder выведет лишнюю запятую. Мы к этому ещё вернёмся. Запускаем проект:
dotnet run -c Release

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

Бенчмарк выдаст подробный отчёт. В начале выдаётся информация о системе, в которой проходили тесты, а затем собственно результаты с разными статистическими подробностями. Мы обратим внимание на колонку Mean (Среднее):
|        Method |     Mean |
|-------------- |---------:|
| StringJoin | 69.71 ns |
| StringBuilder | 41.19 ns |

Как видите, метод, использующий StringBuilder, работает заметно быстрее.

Продолжение следует…

Источник:
https://blog.nimblepros.com/blogs/benchmarking-in-dotnet/
👍13
День 1359. #Testing #Benchmark
Руководство по Бенчмарку в .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. Окончание
Начало
Продолжение

Настройка и очистка
Иногда нужно написать какую-то логику, которая должна выполняться до или после бенчмарка, но мы не хотим, чтобы она участвовала в бенчмарке.
- Метод, помеченный атрибутом [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