В более ранний пост Мы смотрели на производительность указателей функций, представленных в .NET 5/C# 9.0. Для этого мы использовали BenchmarkDotnet ( Документы ), Facto .net Bendcharking Framework. Это дробовик, чтобы заставить вас писать свои собственные тесты- в основном для тестирования регрессии производительности.
Основы
# Install benchmark template dotnet new -i BenchmarkDotNet.Templates # Create new benchmark project `benchmarks` dotnet new benchmark -o benchmarks --console-app -v 0.12.1
- Смотрите Параметры шаблона BenchmarkDotnet
В замешательстве вы найдете ссылки на установку BenchmarkDotnet. Инструмент Глобальный инструмент, который устарел (см. Выпуск 1670 и PR 1572 ). Аналогично, шаблон проекта Dotnet BenchmarkDotnet. Шаблоны по умолчанию: Net Core App 3.0 (которое является из поддержки ), и версия 0.12.0 BenchmarkDotnet (что не поддерживает Net 5.0 и может сбой с системой . NotsupportedException: неизвестно .NET Время выполнения ) Open Benchmarks/Benchmarks.csproj и заменить:
-netcoreapp3.0 +net5.0
Шаблон довольно прост, так что его так же легко запустить dotnet new classlib -o тесты и заполненный Benchmarks.csproj с:
net5.0 Exe AnyCPU portable true true true Release
В Benchmarks/Program.cs , замените основное тело следующим образом, чтобы получить такую же функциональность, как и контрольный CLI (от the Docs ):
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;
namespace benchmarks
{
class Program
{
static void Main(string[] args) =>
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
}
}
Создайте эталон в Benchmarks.cs :
using BenchmarkDotNet;
using BenchmarkDotNet.Attributes;
namespace benchmarks
{
public class Delegate
{
[Benchmark]
public void CallDelegate()
{
// Do nothing for now
}
}
}
Попробуйте запустить тесты:
dotnet run --project ./Benchmarks/benchmarks.csproj -- --filter "benchmarks.*"
Если вы уйдете -Фильтр Он будет ждать ввода от Stdin.
Чтобы увидеть список вариантов CLI:
dotnet run --project ./Benchmarks/benchmarks.csproj -- -h
Более
Неизменно, вы тестируете какую-то другую библиотеку, в отличие от написания автономных тестов. А затем в какой -то момент это может потерпеть неудачу с Ассамблея XXX, которая определяет контрольные показатели, не оптимизированные yyy Анкет Все должно быть собрано для выпуска:
# Add a project reference to assembly we're benchmarking dotnet add benchmarks reference ./nng.NET.Shared/nng.NET.Shared.csproj # Note --configuration dotnet run --project ./Benchmarks/benchmarks.csproj --configuration release -- --filter "benchmarks.*"
Подобно другим структурам тестирования, вы можете иметь инициализацию и очистку (которая исключена из контрольных времен) с [GlobalSetUp] / [GlobalCleanup] и [IterationSetup] / [IteersCleanup] Для поведения для каждого класса и для для перспективы соответственно ( Docs ):
public class Delegate
{
delegate int nng_aio_set_output_delegate(nng_aio aio, int index, IntPtr arg);
nng_aio_set_output_delegate nng_aio_set_output;
[GlobalSetup]
public void GlobalSetup()
{
var handle = DllImport.Load();
var ptr = NativeLibrary.GetExport(handle, "nng_aio_set_output");
nng_aio_set_output = Marshal.GetDelegateForFunctionPointer(ptr);
}
[Benchmark]
public void CallDelegate()
{
var _ = nng_aio_set_output(nng_aio.Null, 9, IntPtr.Zero);
}
}
Есть два основных подхода к определению критериев:
- Конфигурации (в стиле свободного или объекта)
- Класс/функция атрибуты
Который позволяет настроить все виды интересных вещей: эталонные параметры, время выполнения рабочие места (например, .NET Версия , JIT/GC) и т. Д.
Если у вас есть несколько классов, результаты CLI распределены. Чтобы получить один, аккуратный стол в конце:
var config = ManualConfig.Create(DefaultConfig.Instance)
.WithOptions(ConfigOptions.JoinSummary);
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config);
ManualConfig имеет еще много вариантов. Все в Конфигурация может быть указан как CLI аргументы после - Анкет Например, Inbonsummary соответствует -JOIN Анкет Ссылка --h для других.
Быстро итерация за счет использования точности: --работа Сухой , -Runonceperituration , или какая -то комбинация -warmupcount 1-iterationcount 1-InvocationCount 1-UnrollFactor 1 Анкет
Отчеты
По умолчанию результаты CSV, HTML и Markdown записаны в BenchmarkDotnet. Артефакты/результаты/ Анкет Другие форматы возможны с -e и базовый выходной путь может быть указан с помощью -a / -Артафакты Анкет Отметка подходит для таких сообщений, как это, веб -интерфейсы CI и т. Д.:
BenchmarkDotNet=v0.12.1, OS=macOS 11.3.1 (20E241) [Darwin 20.4.0] Intel Core i5-8279U CPU 2.40GHz (Coffee Lake), 1 CPU, 8 logical and 4 physical cores .NET Core SDK=5.0.101 [Host] : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT DefaultJob : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT
| 16.934 нс | Делегат | 0.1687 нс | 16.23 | Calldelegate | 0,1578 нс | 0.23 |
| 6,713 нс | Dllimport | 0,0666 нс | 6.44 | Calldllimport | 0,0590 нс | 0.09 |
| 9.884 нс | Интерфейс | 0,1892 нс | 9.55 | Callinterfacetodllimport | 0.2652 нс | 0.35 |
| 6,764 нс | Указатель | 0.1519 нс | 6.49 | CallfunctionPointer | 0.1347 нс | 0.18 |
| 1.042 нс | Dllimport | 0.0125 нс | 1.00 | Callmanaged | 0.0110 нс | 0.00 |
Чтобы получить графики, установите R языка программирования и использовать -e rplot Анкет Если rscript в твоем Путь Переменная среда, она автоматически выведет несколько файлов PNG, в противном случае вы можете вручную запустить Rscript benchmarkdotnet. Артефакты/результаты/сборки. R :
Сравнения
Что «быстро» (или «медленно»)? Иногда критерии бесполезны без чего -либо, чтобы сравнить их.
Вы можете определить базовая линия :
[Benchmark(Baseline = true)]
public void CallManaged()
{
var _ = Managed(IntPtr.Zero, 9, IntPtr.Zero);
}
// Prevent inline-ing
[MethodImpl(MethodImplOptions.NoInlining)]
static nint Managed(nint _unused0, int _unused1, nint _unused2) => 0;
Наличие базовой линии также дает столбцы «соотношение» и «соотношение» (stddev) на выходе.
BenchmarkDotnet не поддерживает и поддерживает Сравнивая результаты из разных прогонов (то есть до и после изменений кода), но вы можете использовать Результаты от .NET Project:
# Clone repo and build ResultsComparer git clone https://github.com/dotnet/performance dotnet_perf pushd dotnet_perf/src/tools/ResultsComparer dotnet build -c release popd # View CLI options dotnet ./dotnet_perf/artifacts/bin/ResultsComparer/Release/net5.0/ResultsComparer.dll -h # Run benchmarks with full JSON reports dotnet run --project ./Benchmarks/benchmarks.csproj -c release -- -f "benchmarks.*" -e fulljson # Compare with results in ./base dotnet ./dotnet_perf/artifacts/bin/ResultsComparer/Release/net5.0/ResultsComparer.dll --base ./base --diff ./BenchmarkDotNet.Artifacts --threshold '5%'
ResultsComparer требует «полных» отчетов JSON. Итак, вам придется сделать один из:
- Беги с
-e Fulljson - Позвоните
AddExporter (BenchmarkDotnet. Экспортеры. Json. Jsonexporter. Полный)на конфигурации - Применить
[BenchmarkDotnet. Атрибуты. Jsonexporterattribute. Полный]атрибут
-throshold Устанавливает разрешенное изменение между результатами. Это может быть процент или продолжительность времени, как 1 мс Анкет
Stdout содержит результаты уценки:
| тесты. Интерфейс. Callinterfacetodllimport | 112104.1 | 11.05 | 1239001.46 |
Ой, похоже, мой Thread.sleep (1) имел измеримое воздействие.
Результаты Comprerer обладают неожиданным поведением. Он бросает исключение, когда в одной папке есть несколько отчетов, поэтому вам может потребоваться очистить результаты повторяющихся прогонов. И это не работает с результатами «сухой» рабочих мест.
CI
Мы стремимся добавить это как часть наших рабочих процессов Action GitHub, но это одинаково хорошо относится к любой другой платформе CI/CD.
Заманчиво провалить сборку/PR, если какой -либо эталон медленнее. Но иногда это неизбежно с исправлением ошибки или «правильной» реализацией. Если Мы все равно хотели сделать это, проверьте, является ли какой -либо тест «медленнее»:
# Output comparison as CSV `./csv`
dotnet ResultsComparer.dll --base ./base --diff ./BenchmarkDotNet.Artifacts --threshold '5%' --csv ./csv
# Parse csv, give it a header (it doesn't have one), and get 3rd column
$column = Import-csv -Path ./csv -Delimiter ';' -Header 'Benchmark','Source','Result' `
| Select-Object 'Result'
# Check for "Slower"
($column | % { $_.Result }).Contains("Slower")
Что нам действительно понравится, так это краткое изложение в комментариях о запросе на привлечение- аналогично нашим отчетам о покрытии кода. Есть несколько способов добавить комментарий из рабочего процесса:
- GitHub-Script Block
- скручивание/отдых
- Действие на рынке сообщества ( опция1 , опция2 )
Используя HTTP REST, PowerShell работает на всех платформах и не требует внешних зависимостей:
# Run ResultsComparer and capture markdown output
$output = dotnet ResultsComparer.dll --base ./BenchmarkDotNet.Artifacts/ --diff ./diff --threshold '5%'
# Convert string[] output to single string with line-breaks
$body = $output -join '`n'
$secure_token = ConvertTo-SecureString -String $GITHUB_TOKEN -AsPlainText
# POST comment to PR
Invoke-WebRequest `
-Method Post `
-Headers @{accept= 'application/vnd.github.v3+json'} `
-Uri https://api.github.com/repos/$USER/$REPO/issues/$PR_ID/comments `
-Body (ConvertTo-Json @{body=$body}) `
-Authentication OAuth -Token $secure_token
Где $ Pr_id можно получить из $ в рабочем процессе.
Предполагая, что все правильно:
Теперь у нас есть куча, но не очень не остановились на том, как мы хотим, чтобы процесс работал. Похоже на ранее , мы можем прикрепить любой из сгенерированных отчетов с upload-artifact / Скачать-артифакт или Загрузка-смягчение в качестве артефактов построения или выпуска активов соответственно. А затем генерируйте отчеты о сравнении. Но лучше всего использовать широкую гамму мелкозернистых критериев, чтобы поймать регрессии производительности в PRS, а затем меньшее количество более широких, обобщенных тестов, чтобы выделить изменения между выпусками. Это требует дополнительных экспериментов.
Оригинал: «https://dev.to/jeikabu/benchmarking-in-net-or-how-i-learned-to-stop-worrying-and-love-perf-regression-testing-55pn»