时间测试 算法
——.NET数据结构与算法系列之一 数组
追忆,2013年11月12日 数据结构
前言 框架
很久都把数据结构和算法的东西忘完了,最近想重温下这些知识。所以就写了<<数据结构与算法系列》的文章,但愿能和你们共同窗习,共同探讨这方面的知识。但愿你们多多指异。 数据结构和算法
1.时间测试 性能
因为本部份内容采用了一种实用的方法来分析数据结构与算法检测,因此在这里避开了使用大O分析法,而采用运行简单基准测试的方法来代替。这种测试将会说明运行一段代码须要多少秒数(或者不管什么时间单位)。 学习
基准法测试是使用时间测试的方法来衡量运行完整算法所花费的时间长度。如同科学同样,基准测试也像是一门艺术,并且为了得到精准分析须要很当心测试代码的方法。下面就来进行详细讨论。 测试
1.1一个简单化的时间测试 ui
首先时间测试须要一此代码。出于简单的考虑,这里将测试一个有关控制台数组内容的子程序。代码以下所示: spa
1: //创建数组
2: static void BuildArray(int[] arr)3: {4: for (int i = 0; i <= 99999; i++)5: {6: arr[i] = i;7: }8: }9:10: //输出数组
11: static void DisplayNums(int[] arr)12: {13: for (int i = 0; i <= arr.GetUpperBound(0); i++)14: {15: Console.WriteLine(arr[i] + " ");
16: }17: }
为了测试这个子程序,须要建立一个就是,而且把子程序调用时的系统时间赋值给此变量。此外,还须要一个变量用来存储子程序返回时的时间。根据这些内容写出了下述这段代码:
DateTime startTime;
TimeSpan endTime;
startTime = DateTime.Now;
endTime = DateTime.Now.Subtract(startTime);
个人机器配置:CPU 1.9GHz 四核,4G内存,Win7旗舰版,在上面运行代码时,子程序的运行时间大约30秒左右(29.94)。虽然这段代码对执行时间的测试好像挺有道理,可是在.NET环境下运行时间代码是彻底不合适的。为何呢?
首先,代码测量的是从子程序调用开始到子程序返回主程序之间流失的时间。可是测试所测量的时间也包含了与C#程序同时运行的其余进行所用的时间。
其次,时间代码不考虑.NET环境下执行的无用单元收集。在类型.NET这样的运行环境中,系统可能在执行无用单元收集的任何一个时间暂停。时间代码实例没有考虑无用单元收集时间,以及很容易受到无用单元收集影响的结果时间。那么到底应该怎么作呢?
1.2 用于.NET环境的时间测试
在.NET 环境中须要考虑程序运行中的线程以及无用单元收集可能在任什么时候候发生的事实。因此在编写时间测试代码时须要考虑这些状况。
先来看一下如何处理无用单元收集。首先讨论一下无用单元收集的用途。C#语言用有时被称为堆的内存来给参考类型(例如字符串、数组以及类事例对象)分配存储空间。堆是用来保存数据项(前面提到的类型)的内存区域。诸如普通变量这样的值类型则存储在堆栈中。
引用的参考数据也存储在堆栈中,可是实际的数据则是以参考类型的形式存储在堆中。
当声明变量的子程序彻底执行结束时就能够释放掉存储在堆栈中的变量。而另外一方面,存储在堆中的变量则会一直保留到调用无用单元收集进程的时候。当没有引用堆数据的行为时,只有经过无用单元收集才能够移除这些数据。
在程序执行过程当中无用单元收集可能会发生在任什么时候候。然而须要确保在实现时间测试代码时没有运行无用单元收集器。可是也许你们据说过经过强制调用无用单元收集器来进行专门
的无用单元收集。.NET 环境为执行无用单元收集调用提供了专门的对象——GC。为了使系统执行无用单元收集,能够有以下简单书写:
GC.Collect();
可是不是全部都要这样作的。存储在堆中的每个对象都有一个称为finalizer 的专门方法。finalizer 方法是在删除对象以前执行的最后一步。有关finalizer 方法的问题是,这些方法不是按照系统方式运行的。事实上,甚至没法确信对象的finalizer 方法是否真的执行了,可是知道在肯定删除对象以前须要执行此对象的finalizer 方法。为了确信这一点,咱们添加了一行代码来告诉程序等待堆上对象的全部finalizer 方法都运行后再继续。此代码行以下:
GC.WaitForPendingFinalizers();
已经清除了一个障碍,如今就剩下一个问题了——采用正确的线程。在.NET 环境中,程序运行在被称为应用程序域的进程中。这就容许操做系统在同一时间内分开运行每一个不一样的程
序。在进程内,程序或程序的一部分是在线程内运行的。操做系统经过线程来分配程序的执行时间。在用时间测试程序代码时,须要确信正在进行时间测试的代码就在为自身程序分配的进程中,而不在操做系统执行的其余任务里。在.NET 框架下经过使用Process 类能够作到这一点。Process 类拥有的方法容许选取当前的进程、选取程序运行其内的线程,以及选取存储线程开始执行时间的计时器。这些方法中的每个均可以合并成一个调用。此调用会把它的返回值赋值给一个变量用来存储开始时间(TimeSpan 对象)。以下列代码所示(没错,就是两行代码):
TimeSpan startingTime;
startingTime = Process.GetCurrentProcess().Threads[0].UserProcessorTime;
剩下要作的就是在进行时间测试的代码段中止时捕获时间。作法以下:
TimeSpan duration;
duration = Process.GetCurrentProcess().Threads[0].UserProcessorTime.Subtract(startingTime);
如今把全部这些合并成一个程序。此程序的代码和先前测试代码是同样的:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Threading.Tasks;
6: using System.Diagnostics;
7:
8: namespace Chapter1
9: {
10: static class NetTest
11: {
12: public static void Start()
13: {
14: //我的认为这两行代码在如今的程序中不须要(程序的初始化以前与测试程序没有关系)
15: //你们怎么看 探讨一下
16: GC.Collect();
17: GC.WaitForPendingFinalizers();
18:
19: TimeSpan startingTime;
20: startingTime = Process.GetCurrentProcess().Threads[0].UserProcessorTime;
21:
22: TimeSpan duration; ;
23:
24: int[] nums = new int[100000];
25:
26: BuildArray(nums);
27:
28: DisplayNums(nums);
29: DisplayNums(nums);
30: DisplayNums(nums);
31:
32: duration = Process.GetCurrentProcess().Threads[0].UserProcessorTime.Subtract(startingTime);
33: Console.WriteLine("Time:" + duration.TotalSeconds);
34: }
35:
36: //创建数组
37: static void BuildArray(int[] arr)
38: {
39: for (int i = 0; i <= 99999; i++)
40: {
41: arr[i] = i;
42: }
43: }
44:
45: //输出数组
46: static void DisplayNums(int[] arr)
47: {
48: for (int i = 0; i <= arr.GetUpperBound(0); i++)
49: {
50: Console.WriteLine(arr[i] + " ");
51: }
52: }
53: }
54: }
55:
调用便可:NetTest.Start();
采用新改进的时间测试代码后,程序的返回值为2.99。把此数值与先前初版时间测试代码返回的将近30秒的数值进行比较。很明显,这两种时间测试方法之间存在显著差别。于是.NET 环境中的时间测试代码应该使用.NET 方法来作。
1.3 Timing Test类
虽然不须要一个类来运行时间测试代码,可是把代码做为类来重写是有意义的,主要缘由是若是可以减小测试的代码行数量,就可以保证代码的清晰。
Timing 类须要下列数据成员:
1.startingTime——用来存储正在测试的代码的开始时间。
2.duration——用来存储正在测试的代码的终止时间。
straingTime 和duration 这两个成员用来存储时间,并且为这些数据成员选择使用TimeSpan数据类型。这里就采用一种构造器方法,此默认构造器会把数据成员所有置为0。
正如看到的那样,Timing 类是很小的,它只须要少许方法。下面是定义:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Threading.Tasks;
6: using System.Diagnostics;
7:8: namespace Chapter1
9: {10: class Timing
11: {12: //用来存储正在测试的代码的开始时间
13: TimeSpan startingTime;14: //用来存储正在测试的代码的终止时间
15: TimeSpan duration;16:17: public Timing()
18: {19: startingTime = new TimeSpan(0);
20: duration = new TimeSpan(0);
21: }22:23: public void StopTime()24: {25: duration = Process.GetCurrentProcess().Threads[0].UserProcessorTime.Subtract(startingTime);26: }27:28: public void StartTime()29: {30: GC.Collect();31: GC.WaitForPendingFinalizers();32: startingTime = Process.GetCurrentProcess().Threads[0].UserProcessorTime;33: }34:35: public TimeSpan Result()
36: {37: return duration;
38: }39: }40: }41:
这是用Timing 类改写的用来测试DisplayNums 子程序的程序:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Threading.Tasks;
6: using System.Diagnostics;
7: using System.Threading;
8:9: namespace Chapter1
10: {11: //这是用Timing 类改写的用来测试DisplayNums 子程序的程序
12: class NetTestUseTiming
13: {14: public void Start()15: {16: int[] nums = new int[100000];17:18: BuildArray(nums);19:20: Timing tObj = new Timing();
21: tObj.StartTime();22: DisplayNums(nums);23: DisplayNums(nums);24: DisplayNums(nums);25:26: tObj.StopTime();27: Console.WriteLine("time (.NET): " + tObj.Result().TotalSeconds);
28: }29:30: //创建数组
31: static void BuildArray(int[] arr)32: {33: for (int i = 0; i < 100000; i++)34: {35: arr[i] = i;36: }37: }38:39: //输出数组
40: static void DisplayNums(int[] arr)41: {42: for (int i = 0; i <= arr.GetUpperBound(0); i++)43: {44: Console.WriteLine(arr[i] + " ");
45: }46: }47: }48: }49:
经过把时间测试代码移动到类里的方法,这里把主程序的代码行数从减小。显然这样不会从程序中砍掉大量的代码,而比砍掉代码更重要的则是下降了主程序的复杂度。
若是没有类,那么把开始时间赋值给变量的操做就会像下面这样:
startTime = Process.GetCurrentProcess( ).Threads[0].UserProcessorTime;
而若是使用Timing 类,那么把开始时间赋值给类数据成员的方式以下所示:
tObj.startTime( );
经过把冗长的赋值语句封装到类方法内,可使得代码更便于阅读并且出错的可能更小了。
小结
上面的例子中尽管不须要编写整个程序,可是一些程序的代码以及要讨论的库都采用面向对象的方式来编写。
Timing 类提供了简单有效的方法来衡量所要学习的数据结构与算法的性能。
天气仍是挺冷的,手脚冰凉,赶忙睡啦。必定要把这个系列坚持写完。
源程序下载:DataStructAndAlgorithm.zip
参考书箱:<<数据结构与算法>>