Как измерить производительность (перформанс) кода на C#?

Ответ

Для точного измерения производительности в .NET недостаточно просто засечь время. Важно учитывать работу сборщика мусора (GC), JIT-компиляцию и оптимизации.

1. Быстрая проверка: System.Diagnostics.Stopwatch Подходит для грубых оценок и замера операций, длящихся миллисекунды и больше.

using System.Diagnostics;

// Всегда используйте StartNew для чистоты измерения
var sw = Stopwatch.StartNew();

// Код, производительность которого нужно измерить
DoSomeWork();

sw.Stop();
Console.WriteLine($"Время выполнения: {sw.ElapsedMilliseconds} мс");
Console.WriteLine($"Точное время: {sw.ElapsedTicks} тиков");

Важно: Запускайте код много раз в цикле, игнорируйте первый "прогревочный" запуск (JIT-компиляция), усредняйте результаты.

2. Профессиональный бенчмаркинг: BenchmarkDotNet Это стандарт де-факто для надежных измерений. Библиотека автоматически решает проблемы с "прогревом", статистикой, сборкой мусора и многопоточностью.

  • Установите пакет: Install-Package BenchmarkDotNet
  • Создайте класс бенчмарка:
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

[MemoryDiagnoser] // Также анализирует выделение памяти (GC)
[RankColumn]     // Добавляет колонку с ранжированием
public class StringBenchmark
{
    private string data = "Hello, World!";

    [Benchmark]
    public string ToUpperInvariant() => data.ToUpperInvariant();

    [Benchmark]
    public string ToUpper() => data.ToUpper();
}

// Запуск в Main
public class Program
{
    public static void Main(string[] args) => BenchmarkRunner.Run<StringBenchmark>();
}

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

3. Профилировщики (Profilers) Инструменты вроде JetBrains dotTrace, Visual Studio Diagnostic Tools или PerfView показывают не только общее время, но и что именно его тратит: методы, вызовы БД, сборка мусора. Это инструмент для поиска узких мест, а не для точечного измерения.

Ответ 18+ 🔞

А, ну ты хочешь замерить производительность в .NET, да? Ну, брат, это не просто DateTime.Now отнять, тут мозги надо включать, а то наебнёшься на ровном месте. Сейчас разжую.

Смотри, если тебе просто прикинуть, не оторвало ли твоему методу руки, то Stopwatch — твой быстрый друг. Но друг, блядь, подставной, если ты не знаешь, как им пользоваться.

using System.Diagnostics;

// Не создавай просто new Stopwatch() и не вызывай Start()! Это для лохов.
// StartNew — это сразу и создание, и старт. Чисто, аккуратненько.
var sw = Stopwatch.StartNew();

// Тут твой код, который, возможно, жрёт ресурсы как не в себя
DoSomeWork();

sw.Stop();
Console.WriteLine($"Время выполнения: {sw.ElapsedMilliseconds} мс");
Console.WriteLine($"Точное время: {sw.ElapsedTicks} тиков");

Но запомни раз и навсегда: один запуск — это хуйня, а не измерение. JIT-компилятор может в первый раз подумать, GC может внезапно почистить память и всё испортить. Запускай в цикле, тысяч сто раз, первый результат выкидывай нахуй, а остальные усредняй. Иначе получишь цифры, которые только в унитазе смотреть.


А теперь, если ты хочешь не прикинуть, а точно знать, где у тебя бутылочное горлышко, то забудь про эти ручные замеры, как страшный сон. Твой новый бог — BenchmarkDotNet. Это, блядь, целая наука, упакованная в NuGet.

Ставишь пакет и делаешь вот такую красоту:

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

[MemoryDiagnoser] // Этот атрибут — святое. Он покажет, сколько памяти твой код сожрал.
[RankColumn]     // Ранжирование, чтобы сразу видно было, кто быстрее, а кто тормоз.
public class StringBenchmark
{
    private string data = "Hello, World!";

    [Benchmark] // Пометил метод — и он уже в сетке бенчмарка.
    public string ToUpperInvariant() => data.ToUpperInvariant();

    [Benchmark]
    public string ToUpper() => data.ToUpper();
}

public class Program
{
    public static void Main(string[] args) => BenchmarkRunner.Run<StringBenchmark>();
}

Самое главное — запускай это в Release-конфигурации и без отладчика, придурок! Иначе JIT-компилятор будет делать неоптимальный код, и все твои замеры полетят в пизду. После запуска получишь таблицу, где будет среднее время, стандартное отклонение (чтобы видеть, насколько результаты пляшут) и, о чудо, аллокации памяти. Прямо увидишь, какой метод не только медленный, но ещё и мусора после себя кучу оставляет.


Ну и высший пилотаж — это профилировщики. dotTrace, встроенные в Visual Studio Diagnostic Tools или тот же PerfView от Microsoft. Это уже не просто "сколько времени", а "на что именно это время потратилось". Ты увидишь дерево вызовов, поймёшь, что 80% времени твоё приложение жрёт не твой алгоритм, а, например, ходит в базу данных или, прости господи, в цикле создаёт новые строки.

Короче, алгоритм такой:

  1. Быстрая прикидка на глазStopwatch с умом.
  2. Точный, воспроизводимый замерBenchmarkDotNet. Без вариантов.
  3. Поиск конкретной причины тормозов — профилировщик.

Выбирай по задаче, а то будешь, как дурак, DateTime мерить и удивляться, почему в продакшене всё встало колом.