Бенчмаркинг с BenchmarkDotNet
Пишите точные микротесты производительности с BenchmarkDotNet, анализируйте пропускную способность и выделения памяти и избегайте распространённых ошибок.
«Бенчмаркинг с BenchmarkDotNet» — бесплатный урок C# Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C# Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C# Academy содержит 4 уроков всего.
Зачем нужен BenchmarkDotNet
Микротестирование производительности на удивление сложно: разогрев JIT, паузы GC, кэши CPU и планирование ОС создают шум. BenchmarkDotNet автоматически учитывает все эти факторы и выдает статистически надежные результаты.
Установка и первый тест производительности
Добавьте пакет NuGet, создайте класс с методами [Benchmark] и запустите их с помощью BenchmarkRunner.Run. Всегда запускайте тесты производительности в режиме Release.
// dotnet add package BenchmarkDotNet
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
[MemoryDiagnoser] // report allocations
[RankColumn] // rank methods by speed
public class StringBenchmarks
{
[Benchmark(Baseline = true)]
public string Concatenation()
{
var s = "";
for (int i = 0; i < 100; i++) s += i;
return s;
}
[Benchmark]
public string StringBuilder()
{
var sb = new System.Text.StringBuilder();
for (int i = 0; i < 100; i++) sb.Append(i);
return sb.ToString();
}
}
// Program.cs (must run as Release):
BenchmarkRunner.Run<StringBenchmarks>();Чтение таблицы результатов
BenchmarkDotNet выводит таблицу результатов со средним временем, погрешностью, стандартным отклонением, соотношением и объемом выделенной памяти. Здесь объясняется, как интерпретировать каждый столбец.
// Example output:
// | Method | Mean | Error | StdDev | Ratio | Alloc |
// |-------------- |---------:|--------:|--------:|------:|------:|
// | Concatenation | 4,210 ns | 81 ns | 76 ns | 1.00 | 6.4KB |
// | StringBuilder | 312 ns | 4 ns | 4 ns | 0.07 | 1.6KB |
// Mean: average time per operation
// Error: half of 99.9% confidence interval
// StdDev: standard deviation — high value = noisy environment
// Ratio: relative to Baseline=true method (1.00)
// Alloc: managed heap allocated per operation
// Rule of thumb:
// Error should be < 5% of Mean for reliable results
// Run on an isolated machine (no background tasks)Параметризованные тесты производительности
Используйте [Params], чтобы запускать тест производительности для нескольких размеров входных данных. BenchmarkDotNet проверяет каждую комбинацию и создает отдельную строку для каждого значения параметра.
[MemoryDiagnoser]
public class SearchBenchmarks
{
[Params(10, 100, 1000, 10000)]
public int N;
private int[] _data = Array.Empty<int>();
[GlobalSetup]
public void Setup()
=> _data = Enumerable.Range(0, N).ToArray();
[Benchmark]
public bool LinearSearch()
=> Array.IndexOf(_data, N - 1) >= 0;
[Benchmark]
public bool BinarySearch()
=> Array.BinarySearch(_data, N - 1) >= 0;
}Настройка и очистка
Используйте атрибуты жизненного цикла для одноразовой настройки или настройки и очистки перед каждой итерацией. Это исключает стоимость настройки из измеряемого времени теста производительности.
[MemoryDiagnoser]
public class DbQueryBenchmarks
{
private NpgsqlConnection _conn = null!;
private NpgsqlCommand _cmd = null!;
[GlobalSetup] // runs once before all benchmarks
public void Setup()
{
_conn = new NpgsqlConnection("...");
_conn.Open();
_cmd = new NpgsqlCommand("SELECT id FROM orders LIMIT 100", _conn);
}
[IterationSetup] // runs before EACH benchmark iteration
public void IterationSetup() { /* per-iteration prep */ }
[Benchmark]
public int QueryOrders() => (int)_cmd.ExecuteScalar()!;
[GlobalCleanup] // runs once after all benchmarks
public void Cleanup() { _cmd.Dispose(); _conn.Dispose(); }
}Диагностика памяти
[MemoryDiagnoser] показывает выделения в управляемой куче для каждой операции. Используйте его вместе с параметрами [GcForce], чтобы управлять работой GC во время тестов производительности.
[MemoryDiagnoser] // adds Alloc column to results
public class AllocationBenchmarks
{
[Benchmark]
public int[] AllocateArray() => new int[1000];
[Benchmark]
public Span<int> UseStackalloc()
{
Span<int> span = stackalloc int[1000];
return span; // WARN: for illustration; don't return stackalloc
}
[Benchmark]
public int[] ArrayPoolRent()
{
var arr = System.Buffers.ArrayPool<int>.Shared.Rent(1000);
System.Buffers.ArrayPool<int>.Shared.Return(arr);
return arr;
}
}Диагностические средства: ETW и дизассемблирование
BenchmarkDotNet поддерживает несколько диагностических средств. DisassemblyDiagnoser показывает фактический нативный ассемблерный код, что идеально подходит для проверки оптимизаций JIT.
using BenchmarkDotNet.Diagnosers;
[DisassemblyDiagnoser( // show generated ASM
maxDepth: 2,
printSource: true,
exportGithubMarkdown: true)]
[MemoryDiagnoser]
public class VectorizationBenchmarks
{
private float[] _data = new float[1024];
[Benchmark]
public float SumScalar()
{
float sum = 0;
foreach (var v in _data) sum += v;
return sum;
}
[Benchmark]
public float SumVectorized()
=> System.Numerics.Vector<float>.Count > 1
? VectorSum(_data)
: SumScalar();
}Конфигурации заданий
Задания управляют средой выполнения теста производительности: количеством итераций прогрева, количеством вызовов на итерацию, версией среды выполнения .NET и параметрами JIT. Используйте предопределенные или пользовательские задания.
using BenchmarkDotNet.Jobs;
// Compare JIT vs Native AOT performance:
[SimpleJob(RuntimeMoniker.Net90)] // .NET 9 JIT
[SimpleJob(RuntimeMoniker.NativeAot90)] // .NET 9 AOT
[MemoryDiagnoser]
public class RuntimeComparison
{
[Benchmark]
public string SerializeJson()
{
var obj = new { Name = "Alice", Age = 30 };
return System.Text.Json.JsonSerializer.Serialize(obj);
}
}
// Custom job for more warmup:
[Config(typeof(MyConfig))]
public class PreciseBenchmarks
{
private class MyConfig : ManualConfig
{
public MyConfig()
=> AddJob(Job.Default.WithWarmupCount(5).WithIterationCount(20));
}
}Экспорт результатов
BenchmarkDotNet экспортирует результаты в нескольких форматах: Markdown (для запросов на слияние на GitHub), CSV (для электронных таблиц), JSON и HTML-диаграммы. Параметры экспорта настраиваются в конфигурации задания.
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Exporters.Csv;
[Config(typeof(ExportConfig))]
public class MyBenchmarks { /* ... */ }
public class ExportConfig : ManualConfig
{
public ExportConfig()
{
AddExporter(MarkdownExporter.GitHub);
AddExporter(CsvExporter.Default);
AddExporter(HtmlExporter.Default);
// Results in: BenchmarkDotNet.Artifacts/results/
}
}
// CLI usage to run specific benchmarks:
// dotnet run -c Release -- --filter "*StringBenchmarks*"
// dotnet run -c Release -- --list all (list all benchmarks)Практический пример: оптимизация до и после
Использование BenchmarkDotNet для проверки оптимизации производительности — сравнение наивного подхода, подхода с использованием пула и подхода на основе Span.
[MemoryDiagnoser]
[RankColumn]
public class ParsingBenchmarks
{
private const string Input = "2025-06-15T08:30:00Z";
[Benchmark(Baseline = true)]
public DateTime ParseViaString()
=> DateTime.Parse(Input);
[Benchmark]
public DateTime ParseExact()
=> DateTime.ParseExact(Input,
"yyyy-MM-ddTHH:mm:ssZ",
System.Globalization.CultureInfo.InvariantCulture);
[Benchmark]
public bool TryParse()
=> DateTime.TryParseExact(Input.AsSpan(),
"yyyy-MM-ddTHH:mm:ssZ",
System.Globalization.CultureInfo.InvariantCulture,
System.Globalization.DateTimeStyles.None,
out _);
}Быстрая проверка
Почему тесты BenchmarkDotNet необходимо запускать с помощью dotnet run -c Release?
Итоги: тестирование производительности с BenchmarkDotNet
Основные выводы:
- Установите через NuGet, пометьте методы атрибутом
[Benchmark]и запустите с помощьюdotnet run -c Release [MemoryDiagnoser]: добавляет столбец выделений памяти;[RankColumn]: ранжирует результаты по скорости[Params]: запускает тесты для нескольких размеров входных данных, чтобы определить алгоритмическую сложность[GlobalSetup]/[GlobalCleanup]: одноразовая настройка за пределами измеряемого времени[DisassemblyDiagnoser]: позволяет изучить сгенерированный нативный ASM для проверки оптимизаций JIT- Используйте задания для сравнения JIT .NET 9, Native AOT и различного количества итераций
Изучай C# с ИИ-репетитором — бесплатно
Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.
- Курсы
- 93
- Уроки
- 346
Часто задаваемые вопросы
Урок «Бенчмаркинг с BenchmarkDotNet» бесплатный?
Да — полный текст урока «Бенчмаркинг с BenchmarkDotNet» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C# Academy, подпишись на CoddyKit PRO. Курс C# Academy содержит 4 уроков всего.
Чему я научусь в уроке «Бенчмаркинг с BenchmarkDotNet»?
Пишите точные микротесты производительности с BenchmarkDotNet, анализируйте пропускную способность и выделения памяти и избегайте распространённых ошибок. Ты практикуешь C# Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать C# Academy?
Предыдущий опыт не требуется. C# Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Бенчмаркинг с BenchmarkDotNet»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке C# Academy?
Да. Каждый урок C# Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Компиляция Native AOT
- Обрезка и ограничения отражения
- ReadyToRun и многоуровневая компиляция
- Бенчмаркинг с BenchmarkDotNet