Рубрики
Uncategorized

Сравнительный анализ в .net или как я научился перестать беспокоиться и любить ПЕРС -РЕГИССОВАЯ ИСПЫТАНИЯ

В более раннем посте мы смотрели на производительность указателей функций, представленных в .NET 5/C# 9 …. Tagged с Dotnet, Csharp, Performance, DevOps.

В более ранний пост Мы смотрели на производительность указателей функций, представленных в .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. Инструмент Глобальный инструмент, который устарел (см. Выпуск 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")

Что нам действительно понравится, так это краткое изложение в комментариях о запросе на привлечение- аналогично нашим отчетам о покрытии кода. Есть несколько способов добавить комментарий из рабочего процесса:

Используя 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»