精确的时间测量性能testing
什么是最确切的方式来看一些东西,例如方法调用,代码?
最容易和最快的我猜想是这样的:
DateTime start = DateTime.Now; { // Do some work } TimeSpan timeItTook = DateTime.Now - start;
但是这个确实如此呢? 有更好的方法吗?
更好的方法是使用秒表类:
using System.Diagnostics; // ... Stopwatch sw = new Stopwatch(); sw.Start(); // ... sw.Stop(); Console.WriteLine("Elapsed={0}",sw.Elapsed);
正如其他人所说, Stopwatch
是一个很好的类在这里使用。 你可以用一个有用的方法来包装它:
public static TimeSpan Time(Action action) { Stopwatch stopwatch = Stopwatch.StartNew(); action(); stopwatch.Stop(); return stopwatch.Elapsed; }
(注意使用Stopwatch.StartNew()
,我更喜欢创build一个秒表,然后以简单的方式调用Start()
。)显然这招致了调用委托的命中,但在绝大多数情况下,不相关。 你会写:
TimeSpan time = StopwatchUtil.Time(() => { // Do some work });
你甚至可以为此设置一个ITimer
接口,可以使用StopwatchTimer,
CpuTimer
等实现。
正如其他人所说, Stopwatch
应该是正确的工具。 虽然可以做一些改进,但是请特别参阅这个主题: 在C#中对小代码样本进行基准testing,这个实现可以改进吗? 。
我在这里看到了Thomas Maierhofer的一些有用的提示
基本上他的代码如下所示:
//prevent the JIT Compiler from optimizing Fkt calls away long seed = Environment.TickCount; //use the second Core/Processor for the test Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(2); //prevent "Normal" Processes from interrupting Threads Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High; //prevent "Normal" Threads from interrupting this thread Thread.CurrentThread.Priority = ThreadPriority.Highest; //warm up method(); var stopwatch = new Stopwatch() for (int i = 0; i < repetitions; i++) { stopwatch.Reset(); stopwatch.Start(); for (int j = 0; j < iterations; j++) method(); stopwatch.Stop(); print stopwatch.Elapsed.TotalMilliseconds; }
另一种方法是依靠Process.TotalProcessTime
来测量CPU忙于运行该代码/进程的时间 , 如下所示,这可以反映更真实的场景,因为没有其他进程影响测量。 它做类似的事情:
var start = Process.GetCurrentProcess().TotalProcessorTime; method(); var stop = Process.GetCurrentProcess().TotalProcessorTime; print (end - begin).TotalMilliseconds;
在这里可以find相同的裸体,详细的实现。
我写了一个辅助类来以易于使用的方式执行这两个操作:
public class Clock { interface IStopwatch { bool IsRunning { get; } TimeSpan Elapsed { get; } void Start(); void Stop(); void Reset(); } class TimeWatch : IStopwatch { Stopwatch stopwatch = new Stopwatch(); public TimeSpan Elapsed { get { return stopwatch.Elapsed; } } public bool IsRunning { get { return stopwatch.IsRunning; } } public TimeWatch() { if (!Stopwatch.IsHighResolution) throw new NotSupportedException("Your hardware doesn't support high resolution counter"); //prevent the JIT Compiler from optimizing Fkt calls away long seed = Environment.TickCount; //use the second Core/Processor for the test Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(2); //prevent "Normal" Processes from interrupting Threads Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High; //prevent "Normal" Threads from interrupting this thread Thread.CurrentThread.Priority = ThreadPriority.Highest; } public void Start() { stopwatch.Start(); } public void Stop() { stopwatch.Stop(); } public void Reset() { stopwatch.Reset(); } } class CpuWatch : IStopwatch { TimeSpan startTime; TimeSpan endTime; bool isRunning; public TimeSpan Elapsed { get { if (IsRunning) throw new NotImplementedException("Getting elapsed span while watch is running is not implemented"); return endTime - startTime; } } public bool IsRunning { get { return isRunning; } } public void Start() { startTime = Process.GetCurrentProcess().TotalProcessorTime; isRunning = true; } public void Stop() { endTime = Process.GetCurrentProcess().TotalProcessorTime; isRunning = false; } public void Reset() { startTime = TimeSpan.Zero; endTime = TimeSpan.Zero; } } public static void BenchmarkTime(Action action, int iterations = 10000) { Benchmark<TimeWatch>(action, iterations); } static void Benchmark<T>(Action action, int iterations) where T : IStopwatch, new() { //clean Garbage GC.Collect(); //wait for the finalizer queue to empty GC.WaitForPendingFinalizers(); //clean Garbage GC.Collect(); //warm up action(); var stopwatch = new T(); var timings = new double[5]; for (int i = 0; i < timings.Length; i++) { stopwatch.Reset(); stopwatch.Start(); for (int j = 0; j < iterations; j++) action(); stopwatch.Stop(); timings[i] = stopwatch.Elapsed.TotalMilliseconds; print timings[i]; } print "normalized mean: " + timings.NormalizedMean().ToString(); } public static void BenchmarkCpu(Action action, int iterations = 10000) { Benchmark<CpuWatch>(action, iterations); } }
打电话
Clock.BenchmarkTime(() => { //code }, 10000000);
要么
Clock.BenchmarkCpu(() => { //code }, 10000000);
Clock
的最后部分是棘手的部分。 如果你想显示最后的时间,那么你可以select你想要的时间。 我写了一个扩展方法NormalizedMean
,它给你读取时间的均值, 丢弃噪声。 我的意思是我计算每个时间点与实际平均值的偏差,然后从偏差平均值 (称为绝对偏差;注意它不是经常听到的标准偏差)中丢弃更加有用的值(只有较慢的值) ,最后返回剩余值的平均值。 这意味着,例如,如果定时值是{ 1, 2, 3, 2, 100 }
1,2,3,2,100 { 1, 2, 3, 2, 100 }
(毫秒或其他),则丢弃100
,并返回2的平均值{ 1, 2, 3, 2 }
。 或者如果时间是{ 240, 220, 200, 220, 220, 270 }
,则丢弃270
,并且返回{ 240, 220, 200, 220, 220 }
的{ 240, 220, 200, 220, 220 }
{ 240, 220, 200, 220, 220, 270 }
的平均值。
public static double NormalizedMean(this ICollection<double> values) { if (values.Count == 0) return double.NaN; var deviations = values.Deviations().ToArray(); var meanDeviation = deviations.Sum(t => Math.Abs(t.Item2)) / values.Count; return deviations.Where(t => t.Item2 > 0 || Math.Abs(t.Item2) <= meanDeviation).Average(t => t.Item1); } public static IEnumerable<Tuple<double, double>> Deviations(this ICollection<double> values) { if (values.Count == 0) yield break; var avg = values.Average(); foreach (var d in values) yield return Tuple.Create(d, avg - d); }
使用秒表类
System.Diagnostics.Stopwatch是为此任务而devise的。
秒表不错,可循环10 ^ 6次,再除以10 ^ 6。 你会得到更多的精度。
我正在使用这个:
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(myUrl); System.Diagnostics.Stopwatch timer = new Stopwatch(); timer.Start(); HttpWebResponse response = (HttpWebResponse)request.GetResponse(); statusCode = response.StatusCode.ToString(); response.Close(); timer.Stop();
从我的博客: C#时间测量性能testing (不是英文)
是的,Windows内核上有一些function
[System.Runtime.InteropServices.DllImport("KERNEL32")] private static extern bool QueryPerformanceCounter(ref long lpPerformanceCount); [System.Runtime.InteropServices.DllImport("KERNEL32")] private static extern bool QueryPerformanceFrequency(ref long lpFrequency); public static float CurrentSecond { get { long current = 0; QueryPerformanceCounter(ref current); long frequency = 0; QueryPerformanceFrequency(ref frequency); return (float) current / (float) frequency; } }