BenchmarkDotNet и микробенчмарки
↑ 7.1 Производительность .NET и кэширование · ← Предыдущая · → Следующая
Зачем это на собесе
Как доказывать, что оптимизация работает; типичные ошибки бенчмарков.
Объяснение
BenchmarkDotNet запускает код в изолированном процессе, прогревает JIT, делает много итераций и считает статистику.
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net90)]
public class ConcatBench
{
private readonly string[] _parts = Enumerable.Range(0, 100).Select(i => i.ToString()).ToArray();
[Benchmark(Baseline = true)]
public string Plus() { var s = ""; foreach (var p in _parts) s += p; return s; }
[Benchmark]
public string Builder() { var sb = new StringBuilder(); foreach (var p in _parts) sb.Append(p); return sb.ToString(); }
[Benchmark]
public string Join() => string.Concat(_parts);
}
BenchmarkRunner.Run<ConcatBench>(); // dotnet run -c ReleaseРезультат: среднее время, ошибка, StdDev, отношение к baseline, Gen0/Gen1/Gen2 и Allocated.
Нюансы и подводные камни
- Только Release без отладчика.
- JIT может удалить код без побочных эффектов (dead code elimination): возвращайте результат.
- Параметризуйте размеры (
[Params]) — оптимизация может ломаться на других объёмах. - Микробенчмарк не заменяет измерение в реальной системе: сеть, БД и GC ведут себя иначе.
- Стабильность: закройте фоновые процессы, не сравнивайте между машинами.
- Тиеринг JIT (
TieredPGO) влияет на результаты: используйте warmup, как делает BDN.
Практика
- Сравните
string +,StringBuilder,string.ConcatсMemoryDiagnoser. - Сравните
foreachпоList<T>и поIEnumerable<T>. - Встройте бенчмарк в отдельный проект и храните результаты в репозитории.
Вопросы с ответами
Почему нельзя измерять через Stopwatch в цикле?
Не учитывается прогрев JIT, тиеринг, GC и шум; BDN обрабатывает всё это статистически.
Что означает Gen0 в отчёте?
Количество сборок мусора поколения 0 на 1000 операций — оценка нагрузки на GC.