关于c#:性能测试的精确时间测量

Exact time measurement for performance testing

本问题已经有最佳答案,请猛点这里访问。

查看某些内容(例如方法调用)接受代码的最精确方式是什么?

我猜的最容易和最快的是:

1
2
3
4
5
DateTime start = DateTime.Now;
{
    // Do some work
}
TimeSpan timeItTook = DateTime.Now - start;

但这有多精确? 还有更好的方法吗?


更好的方法是使用秒表类:

1
2
3
4
5
6
7
8
9
10
11
12
using System.Diagnostics;
// ...

Stopwatch sw = new Stopwatch();

sw.Start();

// ...

sw.Stop();

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


正如其他人所说的那样,Stopwatch在这里是一个很好的类。你可以用一个有用的方法包装它:

1
2
3
4
5
6
7
public static TimeSpan Time(Action action)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    action();
    stopwatch.Stop();
    return stopwatch.Elapsed;
}

(注意使用Stopwatch.StartNew()。我更喜欢这个来创建一个秒表然后在简单性方面调用Start()。)显然这会引起调用委托的命中,但在绝大多数情况下都不会相关。然后你写:

1
2
3
4
TimeSpan time = StopwatchUtil.Time(() =>
{
    // Do some work
});

您甚至可以为此创建一个ITimer接口,其中StopwatchTimer, CpuTimer等的实现可用。


正如其他人所说,Stopwatch应该是正确的工具。但是对它做了很少的改进,具体看到这个线程:在C#中对小代码样本进行基准测试,是否可以改进这种实现?

我在这里看到了Thomas Maierhofer的一些有用的提示

基本上他的代码看起来像:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//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保持忙于运行代码/进程的时间,如此处所示。这可以反映更真实的情况,因为没有其他进程影响测量。它做的事情如下:

1
2
3
4
 var start = Process.GetCurrentProcess().TotalProcessorTime;
 method();
 var stop = Process.GetCurrentProcess().TotalProcessorTime;
 print (end - begin).TotalMilliseconds;

在这里可以找到相同的裸体,详细的实现。

我编写了一个帮助程序类,以易于使用的方式执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
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);
    }
}

打电话吧

1
2
3
4
5
Clock.BenchmarkTime(() =>
{
    //code

}, 10000000);

要么

1
2
3
4
5
Clock.BenchmarkCpu(() =>
{
    //code

}, 10000000);

Clock的最后一部分是棘手的部分。如果您想显示最终时间,可由您自行选择所需的时间。我写了一个扩展方法NormalizedMean,它给出了丢弃噪声的读取时间的平均值。我的意思是我计算每个时间与实际平均值的偏差,然后我从偏差的平均值(称为绝对偏差;请注意它不是经常听到的标准偏差)中丢弃更快的值(只有较慢的值) ,最后返回剩余值的平均值。这意味着,例如,如果定时值{ 1, 2, 3, 2, 100 }(以ms或其他为单位),则丢弃100,并返回{ 1, 2, 3, 2 }的平均值2。或者,如果时间{ 240, 220, 200, 220, 220, 270 },则丢弃270,并返回{ 240, 220, 200, 220, 220 }的平均值220

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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专为此任务而设计。


秒表很好,但循环工作10 ^ 6次,然后除以10 ^ 6。
你会得到更多的精确度。


我正在使用这个:

1
2
3
4
5
6
7
8
9
10
11
12
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#性能测试时间测量(不是英文)


是的,Windows内核上有一些功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[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;
    }
}