Я провожу очень быстрые и грязные тесты на одной строке кода 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, очевидно, недостаточно детализирован.
Итак, как мне лучше измерить производительность?
возможный дубликат Можно ли улучшить эту реализацию при тестировании небольших образцов кода на C#?
Возможный дубликат Измерение времени выполнения кода





Выполняйте свой код несколько раз. Проблема, похоже, в том, что ваш код выполняется намного быстрее, чем детализация вашего измерительного прибора. Самое простое решение - выполнить ваш код много-много раз (тысячи, может быть, миллионы), а затем вычислить среднее время выполнения.
Обновлено: Кроме того, из-за характера текущих оптимизирующих компиляторов (и виртуальных машин, таких как CLR и JVM) измерение скорости выполнения отдельных строк кода может вводить в заблуждение, поскольку измерение может сильно влиять на скорость . Гораздо лучшим подходом было бы профилировать всю систему (или, по крайней мере, более крупные блоки) и проверить, где находятся узкие места.
Используйте настоящий профилировщик, например dotTrace.
Класс Stopwatch, доступный с .NET 2.0, - лучший способ сделать это. Это высокопроизводительный счетчик с точностью до долей миллисекунды.
Взгляните на Документация MSDN, это довольно ясно.
Обновлено: как было предложено ранее, также рекомендуется запустить код несколько раз, чтобы получить разумное среднее время.
Вы можете воспользоваться функцией VS2015, где вы видите время, прошедшее между двумя точками останова во время отладки: blogs.msdn.com/b/visualstudioalm/archive/2015/07/20/…
Вы можете использовать Stopwatch, если используете .NET 2.0 или новее.
System.Diagnostics.Stopwatch.StartNew();
Класс Stopwatch также имеет общедоступное поле IsHighResolution, доступное только для чтения, которое позволит вам узнать, основан ли секундомер на счетчике производительности с высоким разрешением. В противном случае это основано на системном таймере.
Я не уверен, что нужно, чтобы секундомер был основан на счетчике производительности с высоким разрешением. Есть несколько вызовов API, но я полагаю, что если секундомер не использует высокое разрешение, то API, вероятно, там нет.
См. Ответ на DateTime.Now - лучший способ измерить производительность функции? для объяснения или прочтите мой сообщение в блоге о высокопроизводительном измерении
Проблема в том, что DateTime имеет разрешение около 15 мс, точнее не может быть. А секундомер может.
Вот хорошая запись в MSDN о том, как Внедрение постоянно обновляемого поставщика времени с высоким разрешением для Windows
Вот образец исходного кода для статьи (C++).
Иногда может быть лучше посмотреть, почему вам нужно рассчитать время операции? Он работает медленно? Или тебе просто любопытно? Первое правило оптимизации - «Не делай этого». Итак, в зависимости от того, что вы на самом деле измеряете, может измениться мнение о том, какой инструмент лучше всего подходит для этой задачи.
Конечно, это не то, что войдет в производственный код. Просто кое-что мне нужно протестировать, прежде чем внедрять «настоящий» код в производство.
Иногда некоторые разработчики думают, например, что простое «if (false)» будет «слишком дорого». Нам нужно показать им, что такие инструкции действительно дешевы по сравнению с большинством обычных операций.
Просто чтобы добавить к тому, что другие уже сказали об использовании секундомера и измерения средних значений.
Убедитесь, что вы вызываете свой метод перед измерением. В противном случае вы также будете измерять время, необходимое для 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 - отличный инструмент
Я сделал расширение, которое возвращает миллисекунды из тиков.
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
потому что шесть лет назад, когда я опубликовал это, я не знал лучшего
Пример для класса 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;
}
}
}
}
Примеры использования помогли бы лучше оценить полезность этого класса.
образцы об этом?
https://andreyakinshin.gitbooks.io/performancebookdotnet/content/science/microbenchmarking.html
https://github.com/PerfDotNet/BenchmarkDotNet
«Действительно, микробенкмаркинг - это очень сложно. Если операция занимает 10–100 нс, измерение операций является большой проблемой. Я предлагаю вам использовать BenchmarkDotNet для ваших тестов. Это библиотека, которая может помочь вам провести честный тест и получить измерения с хорошая точность. Конечно, вы можете написать собственный тест без каких-либо дополнительных библиотек. В этом разделе мы поговорим о том, почему это, вероятно, плохая идея, и о том, что вы должны знать перед началом ».
Очень просто использовать, вот мой пример использования: gist.github.com/danield137/57d4b16b23f409e1198c4690e36b5fd5
Первая ссылка в ответе мертва - "404 | Книга андреякиншин / performancebookdotnet не найдена".
Другой вариант - автоматическая вставка кода таймера с помощью 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>();
}
}
}
Если тики недостаточно детализированы, я бы не стал слишком беспокоиться о производительности 8)