Как измерить производительность кода в .NET?

Я провожу очень быстрые и грязные тесты на одной строке кода C# с использованием DateTime:

long lStart = DateTime.Now.Ticks;
// do something
long lFinish = DateTime.Now.Ticks;

Проблема в результатах:

Start Time [633679466564559902]
Finish Time [633679466564559902]

Start Time [633679466564569917]
Finish Time [633679466564569917]

Start Time [633679466564579932]
Finish Time [633679466564579932]

...и так далее.

Учитывая, что время начала и окончания идентичны, Ticks, очевидно, недостаточно детализирован.

Итак, как мне лучше измерить производительность?

Если тики недостаточно детализированы, я бы не стал слишком беспокоиться о производительности 8)

johnc 20.01.2009 00:15

Возможный дубликат Измерение времени выполнения кода

Michael Freidgeim 19.06.2017 06:39
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
48
3
23 813
18
Перейти к ответу Данный вопрос помечен как решенный

Ответы 18

Выполняйте свой код несколько раз. Проблема, похоже, в том, что ваш код выполняется намного быстрее, чем детализация вашего измерительного прибора. Самое простое решение - выполнить ваш код много-много раз (тысячи, может быть, миллионы), а затем вычислить среднее время выполнения.

Обновлено: Кроме того, из-за характера текущих оптимизирующих компиляторов (и виртуальных машин, таких как CLR и JVM) измерение скорости выполнения отдельных строк кода может вводить в заблуждение, поскольку измерение может сильно влиять на скорость . Гораздо лучшим подходом было бы профилировать всю систему (или, по крайней мере, более крупные блоки) и проверить, где находятся узкие места.

Используйте настоящий профилировщик, например dotTrace.

Ответ принят как подходящий

Класс Stopwatch, доступный с .NET 2.0, - лучший способ сделать это. Это высокопроизводительный счетчик с точностью до долей миллисекунды. Взгляните на Документация MSDN, это довольно ясно.

Обновлено: как было предложено ранее, также рекомендуется запустить код несколько раз, чтобы получить разумное среднее время.

Вы можете воспользоваться функцией VS2015, где вы видите время, прошедшее между двумя точками останова во время отладки: blogs.msdn.com/b/visualstudioalm/archive/2015/07/20/…

Dave 30.07.2015 12:15

Вы можете использовать Stopwatch, если используете .NET 2.0 или новее.

System.Diagnostics.Stopwatch.StartNew();

Класс Stopwatch также имеет общедоступное поле IsHighResolution, доступное только для чтения, которое позволит вам узнать, основан ли секундомер на счетчике производительности с высоким разрешением. В противном случае это основано на системном таймере.

Я не уверен, что нужно, чтобы секундомер был основан на счетчике производительности с высоким разрешением. Есть несколько вызовов API, но я полагаю, что если секундомер не использует высокое разрешение, то API, вероятно, там нет.

См. Ответ на DateTime.Now - лучший способ измерить производительность функции? для объяснения или прочтите мой сообщение в блоге о высокопроизводительном измерении

Проблема в том, что DateTime имеет разрешение около 15 мс, точнее не может быть. А секундомер может.

Иногда может быть лучше посмотреть, почему вам нужно рассчитать время операции? Он работает медленно? Или тебе просто любопытно? Первое правило оптимизации - «Не делай этого». Итак, в зависимости от того, что вы на самом деле измеряете, может измениться мнение о том, какой инструмент лучше всего подходит для этой задачи.

Конечно, это не то, что войдет в производственный код. Просто кое-что мне нужно протестировать, прежде чем внедрять «настоящий» код в производство.

Scott Marlowe 31.01.2009 18:30

Иногда некоторые разработчики думают, например, что простое «if (false)» будет «слишком дорого». Нам нужно показать им, что такие инструкции действительно дешевы по сравнению с большинством обычных операций.

Elo 11.12.2019 17:07

Просто чтобы добавить к тому, что другие уже сказали об использовании секундомера и измерения средних значений.

Убедитесь, что вы вызываете свой метод перед измерением. В противном случае вы также будете измерять время, необходимое для JIT-компиляции кода. Это может значительно исказить ваши цифры.

Кроме того, убедитесь, что вы измеряете код режима выпуска, поскольку оптимизация по умолчанию отключена для отладочных сборок. Настраивать отладочный код имхо бессмысленно.

И убедитесь, что вы измеряете то, что действительно хотите измерить. Когда начинается оптимизация, компилятор / JIT-компилятор может переупорядочить код или полностью удалить его, так что вы можете в конечном итоге измерить что-то немного другое, чем предполагалось. По крайней мере, взгляните на сгенерированный код, чтобы убедиться, что код не был удален.

В зависимости от того, что вы пытаетесь измерить, имейте в виду, что реальная система будет нагружать среду выполнения иначе, чем типичное тестовое приложение. Некоторые проблемы с производительностью связаны, например, с как объекты собираются мусором. Эти проблемы обычно не проявляются в простом тестовом приложении.

На самом деле, лучший совет - измерять реальные системы реальными данными, поскольку тесты в песочнице могут оказаться очень неточными.

Я считаю это полезным

http://accelero.codeplex.com/SourceControl/changeset/view/22633#290971http://accelero.codeplex.com/SourceControl/changeset/view/22633#290973http://accelero.codeplex.com/SourceControl/changeset/view/22633#290972

TickTimer - это урезанная копия секундомера, которая запускается при создании и не поддерживает перезапуск. Он также сообщит вам, если текущее оборудование не поддерживает синхронизацию с высоким разрешением (секундомер проглатывает эту проблему)

Так что это

var tickTimer = new TickTimer();
//call a method that takes some time
DoStuff();
tickTimer.Stop();
Debug.WriteLine("Elapsed HighResElapsedTicks " + tickTimer.HighResElapsedTicks);
Debug.WriteLine("Elapsed DateTimeElapsedTicks " + tickTimer.DateTimeElapsedTicks);
Debug.WriteLine("Elapsed ElapsedMilliseconds " + tickTimer.ElapsedMilliseconds);
Debug.WriteLine("Start Time " + new DateTime(tickTimer.DateTimeUtcStartTicks).ToLocalTime().ToLongTimeString());

выведет это

Elapsed HighResElapsedTicks 10022886
Elapsed DateTimeElapsedTicks 41896
Elapsed ElapsedMilliseconds 4.18966178849554
Start Time 11:44:58

DebugTimer - это оболочка для TickTimer, которая запишет результат в Debug. (примечание: он поддерживает одноразовый шаблон)

Так что это

using (new DebugTimer("DoStuff"))
{
    //call a method that takes some time
    DoStuff();
}

выведет это в окно отладки

DoStuff: Total 3.6299 ms

IterationDebugTimer предназначен для измерения времени, необходимого для выполнения операции несколько раз и записи результата в Debug. Он также выполнит начальный запуск, который не включен, чтобы игнорировать время запуска. (примечание: он поддерживает одноразовый шаблон)

Так что это

int x;
using (var iterationDebugTimer = new IterationDebugTimer("Add", 100000))
{
    iterationDebugTimer.Run(() =>
    {
        x = 1+4;
    });
}

Выведет это

Add: Iterations 100000 
Total 1.198540 ms 
Single 0.000012 ms

Проще всего использовать профилировщик, например Профилировщик производительности ANTS, или один из других доступных.

и небольшое обновление: ANTS не бесплатный, цены начинаются от 400 долларов. Секундомер бесплатный: P, но я признаю, что ANTS - отличный инструмент

quetzalcoatl 24.11.2012 21:22

Я сделал расширение, которое возвращает миллисекунды из тиков.

public static int GetTotalRunningTimeInMilliseconds(this DateTime start)
{
    var endTicks = DateTime.Now.Ticks - start.Ticks;
    return TimeSpan.FromTicks(endTicks).Milliseconds;
}

Применение:

 var start = DateTime.Now;

 //...your long running code here

 var endTime = start.GetTotalRunningTimeInMilliseconds();

Зачем нужны тики и конвертер? duration = (DateTime.Now - начало) .TotalMilliseconds

Elo 11.12.2019 17:01

потому что шесть лет назад, когда я опубликовал это, я не знал лучшего

Amc_rtty 11.12.2019 21:54

Пример для класса Stopwatch

    using System.Diagnostics;
    ......
    ...
    ..
    Stopwatch sw = new Stopwatch();
    sw.Start();
    //Your Code Here

    sw.Stop();
    Console.WriteLine("Elapsed = {0}",sw.Elapsed);

Для измерения производительности с разницей между измерениями я использую этот класс. В классе StopWatch нет метода Split.

/// <summary>
/// Stopwatch like class that keeps track of timelapses.
/// Probably low-res because of the usage of DateTime.
/// </summary>
public class ChronoMeter
{
    /// <summary>
    /// The name given when the Chronometer was constructed.
    /// </summary>
    public string Name { get; private set; }
    /// <summary>
    /// The moment in time Start was called.
    /// </summary>
    public DateTime Started { get; private set; }

    /// <summary>
    /// All time recordings are added to this list by calling Split and Stop.
    /// </summary>
    public List<ChronoRecord> Records { get; private set; }

    private readonly Stopwatch _stopWatch = new Stopwatch();

    private bool _hasBeenStopped = false;

    /// <summary>
    /// Constrcutor
    /// </summary>
    /// <param name = "pName">The name is used in logging</param>
    /// <param name = "pLoggingType">The type of logging appriate for the information yielded by this time recording.</param>
    public ChronoMeter(string pName)
    {
        Name = pName;
        Records = new List<ChronoRecord>();
    }

    /// <summary>
    /// Not calling Stop is bad practise. Therefore a little safety net zo the end is still recorderd.
    /// Keep in mind that the garbase collector invokes the destructor, so the moment of time probably doesn't make much sense.
    /// It is more to notify that you should have used Stop for the latest split.
    /// </summary>
    ~ChronoMeter()
    {
        if (!_hasBeenStopped)
        {
            Stop("Destructor safety net");
        }
    }

    /// <summary>
    /// TimeElapsedSinceStart of a ChronoRecord is relative to the moment ChronoMeter was started by calling this function.
    /// </summary>
    public void Start()
    {
        _stopWatch.Start();
        Started = DateTime.Now;
    }

    /// <summary>
    /// Splits the timerecording and add a record of this moment to the list of split records.
    /// </summary>
    /// <param name = "pSplitName"></param>
    public void Split(string pSplitName)
    {
        _stopWatch.Stop();
        var created = Started + _stopWatch.Elapsed;
        var previousRecord = Records.LastOrDefault();
        Records.Add(new ChronoRecord(pSplitName, Started, created, previousRecord));
        _stopWatch.Start();
    }

    /// <summary>
    /// Indicates you are done and the records will be written to the log.
    /// </summary>
    public void Stop(string pSplitName)
    {
        Split(pSplitName);
        _stopWatch.Stop();
        _hasBeenStopped = true;
    }

    public class ChronoRecord
    {
        public string Name { get; private set; }
        public TimeSpan TimeElapsedSinceStart { get; private set; }
        public TimeSpan TimeElapsedSincePrevious { get; private set; }
        public DateTime Start { get; private set; }
        public DateTime Created { get; private set; }

        public ChronoRecord(string pName, DateTime pStartDateTime, DateTime pCreated, ChronoRecord pPreviousRecord=null)
        {
            if (pCreated == default(DateTime)) //Ignore DefaultDateTimeComparison
            {
                pCreated = DateTime.Now;
            }
            Created = pCreated;
            Name = pName;
            Start = pStartDateTime;

            TimeElapsedSinceStart = Created - Start;
            if (pPreviousRecord != null)
            {
                TimeElapsedSincePrevious = Created - pPreviousRecord.Created;
            }
            else
            {
                TimeElapsedSincePrevious = TimeElapsedSinceStart;
            }
        }
    }
}

Примеры использования помогли бы лучше оценить полезность этого класса.

nawfal 29.06.2016 21:45

образцы об этом?

Kiquenet 19.03.2020 19:37

https://andreyakinshin.gitbooks.io/performancebookdotnet/content/science/microbenchmarking.html

https://github.com/PerfDotNet/BenchmarkDotNet

«Действительно, микробенкмаркинг - это очень сложно. Если операция занимает 10–100 нс, измерение операций является большой проблемой. Я предлагаю вам использовать BenchmarkDotNet для ваших тестов. Это библиотека, которая может помочь вам провести честный тест и получить измерения с хорошая точность. Конечно, вы можете написать собственный тест без каких-либо дополнительных библиотек. В этом разделе мы поговорим о том, почему это, вероятно, плохая идея, и о том, что вы должны знать перед началом ».

Очень просто использовать, вот мой пример использования: gist.github.com/danield137/57d4b16b23f409e1198c4690e36b5fd5

Daniel Dubovski 24.11.2019 12:25

Первая ссылка в ответе мертва - "404 | Книга андреякиншин / performancebookdotnet не найдена".

Pang 09.11.2020 10:41

Другой вариант - автоматическая вставка кода таймера с помощью Fody. Это делает ваш код намного более легким для чтения, поскольку он разделяет ваши общие проблемы. Я думаю, что это похоже на то, что называется Аспектно-ориентированное программирование, но выполняется во время после компиляции.

См. https://github.com/Fody/MethodTimer для аддона fody, который выполняет синхронизацию метода.

Цитата из Readme:

С перехватчиком где-нибудь в вашей сборке:

public static class MethodTimeLogger {
  public static void Log(MethodBase methodBase, long milliseconds)
  {
    //Do some logging here
  } 
}

Ваш код,

public class MyClass
{
    [Time]
    public void MyMethod()
    {
        //Some code u are curious how long it takes
        Console.WriteLine("Hello");
    }
}

Скомпилировано так:

public class MyClass
{
    public void MyMethod()
    {
        var stopwatch = Stopwatch.StartNew();
        try
        {
            //Some code u are curious how long it takes
            Console.WriteLine("Hello");
        }
        finally
        {
            stopwatch.Stop();
            MethodTimeLogger.Log(methodof(MyClass.MyMethod), stopwatch.ElapsedMilliseconds);
        }
    }
}

Я сделал очень простой метод, который измеряет скорость выполнения Действие, который имеет для меня то преимущество, что я могу повторно использовать его, когда мне это нужно, и какой бы код я ни измерял.

Для меня DateTime было достаточно, но его легко адаптировать с DateTime на Секундомер.

public static TimeSpan MeasureTime(Action action)
{
    DateTime start = DateTime.Now;

    if (action == null)
    {
        throw new ArgumentNullException("action");
    }

    try
    {
        action();
    }
    catch (Exception ex)
    {
        Debugger.Log(1, "Measuring",ex.ToString());
    }

    return DateTime.Now - start;
}

Как это использовать?:

private static void StressTest()
{
    List<TimeSpan> tss = new List<TimeSpan>();

    for (int i = 0; i < 100; i++)
    {
        // here is the measuring:
        var ts = MeasureTime(() => instance.Method("param1"));

        tss.Add(ts);
    }

    Console.WriteLine("Max: {0}", tss.Max());
    Console.WriteLine("Min: {0}", tss.Min());
    Console.WriteLine("Avg: {0}", TimeSpan.FromMilliseconds(tss.Average(i => i.TotalMilliseconds)));
}

или же:

var ts = MeasureTime(() =>
                        {
                            // Some intensive stuff here
                            int a = 1;

                            // more here
                            int b = 2;

                            // and so on
                        });

Одноразовый стиль Stopwatch мне подходит лучше всего.

class VWatch : IDisposable {
    Stopwatch watch = new Stopwatch();
    public VWatch() {
        this.watch.Start();
    }
    public void Dispose() {
        this.watch.Stop();
        Console.WriteLine("Finished. Elapsed = {0}", this.watch.Elapsed);
    }
}

А потом:

using (new VWatch()) {
    /// do something for time measurement
}

Я предпочитаю BenchmarkDotNet, упомянутый @Evgeniy. Вероятно, его ответ был обойден, потому что здесь нет фрагмента кода, но поскольку это сложный элемент, стоит хотя бы заглянуть в библиотеку, прежде чем приступать к чему-то индивидуальному.

А поскольку какой-то код всегда бросается в глаза, вот пример с цитируемого сайта:

using System;
using System.Security.Cryptography;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

namespace MyBenchmarks
{
    [ClrJob(baseline: true), CoreJob, MonoJob, CoreRtJob]
    [RPlotExporter, RankColumn]
    public class Md5VsSha256
    {
        private SHA256 sha256 = SHA256.Create();
        private MD5 md5 = MD5.Create();
        private byte[] data;

        [Params(1000, 10000)]
        public int N;

        [GlobalSetup]
        public void Setup()
        {
            data = new byte[N];
            new Random(42).NextBytes(data);
        }

        [Benchmark]
        public byte[] Sha256() => sha256.ComputeHash(data);

        [Benchmark]
        public byte[] Md5() => md5.ComputeHash(data);
    }

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

Другие вопросы по теме