怎样测试程序的平均性能
标准库中的sort函数,是快速排序算法的典型实现。算法将含有n个元素的序列排序,平均需要 O(n log n) 时间。
上周,我提出了“测试一个程序的性能比测试其功能更难”这个观点。确认程序的性能达到标准以及确定“标准”的含义都十分困难。
接下来,我会继续讨论标准库中的sort(排序)函数。sort函数实现了快速排序算法,快速排序算法平均可以在 O(n log n) 时间内对含有n个元素的序列进行排序。除了这个平均性能之外,如果选择了“不幸”的输入情况,快速排序的运行时间会比平均时间长很多,比如,某些情况下快速排序的时间复杂度可以达到O(n2)。我使用“不幸”这个词是因为在快速排序的实现中经常使用随机性的来保证O(n2)这样的性能表现很少出现。
为什么随机性在这里很重要呢?快速排序算法开始时挑选序列中的一个特定元素开始排序,叫做pivot(中心数据)。然后,快速排序算法调整元素的顺 序,使得小于等于中心数据的元素位于中心数据的前面,所有大于中心数据的元素排在中心数据的后面。最后,快速排序算法递归调用,来完成对这两部分元素的排 序
因此,快速排序的执行时间,最坏情况下与元素个数对应的最大递归深度成比例。在实现中,依赖于递归深度的快速排序的性能,一般不会大于O(log n)。只要中心数据选定,递归深度就可以估计。平均情况下,这个深度与最大或最小元素的值无关。
快速排序算法是怎样确保选定的中心数据不是很接近序列的端点呢?一般来看,这是无法保证的。尽管如此,大部分情况下可以采用随机选取中心数据的方法,来避 免出现最坏情况。这样做可以保证快速排序算法的平均性能是可以接受的,即使在个别情况下,中心数据会出现在序列的端点位置,从而导致算法性能低下。由于这 样的情况非常少见,对于平均性能来说它不是一个大问题。对吗?
这要视情况而定。假设,你的任务是编写性能测试程序,来测试快速排序的实现。
- 你怎样将c++标准中模糊的“平均性能”,改写为实际需求,从而可以测试所有情况?
- 你以怎样的方式来测试快速排序,这样的方式可以保证测试结果正确可靠?
测试平均性能之所以困难,是因为在这个概念中一个概率的因素。如果,程序最终必须产生一个特定结果,那么,你可以确定一个测试程序的运行结果是正确 还是错误。相反,如果你在测试平均性能,那么对于一个单独的测试用例,无法判断运行结果是否正确。最好情况是,通过运行越来越多的测试用例,你可以更有把 握程序是否正确运行。在这个测试的过程中,更多的测试可能会改变你对于程序正确性的判断。
简而言之,如果性能中包括了关于平均执行时间的描述,那么相应的测试需要用到一些统计分析。这样的分析并不简单,但是这是工程应用的传统。美国航空191号班机的空难就 是一个例子。191号班机在1979年5月25日从奥黑尔国际机场起飞。当飞机刚刚离开地面时,飞机左翼引擎忽然失灵并且从机翼上脱落。引擎是通过安全销 连接在机翼上的,这样的设计是为了与机翼脱离而不是毁坏机翼。尽管如此,由于维护失误,机翼被毁,导致飞机失控,发生空难,机上所有人无一幸免。
在阅读相关的调查中,我看见了不同的飞机制造商对安全销进行的测试,证明— 假设飞机正常维护 — 安全销会使得引擎离开机翼而不是毁坏机翼。在此之前,我没有见到过这样的测试,但是设计安全销在工程商的主要问题是:安全销的目的是,受到过大压力时,使 引擎和机翼脱落。没有办法在安全销不被破坏的情况下测试安全销是否满足要求。因此,在飞机中实际使用的安全销不能检测。
人们怎样才能确保使用这样生产的安全销不会影响飞机的安全性呢?答案设计非常聪明。
- 引擎被许多个安全销固定在机翼上,这样即使有一个安全销失灵,引擎也能从机翼上分离,而不损坏机翼。
- 安全销以100个为一批进行生产,一个批次内的安全销同时以同样的方式生产制造。
- 每个批次的100个安全销中,会被随机挑选10个进行测试,因此,10个安全销会在测试中毁坏。如果,10个安全销都通过了测试,那么就认为剩下的90个安全销是安全可用的。如果10个中有一个安全销没有通过测试,那么这个批次都会被销毁。
显然,这样的设计不仅仅包括了巧妙的工业设计,也包含了精妙的统计分析。必须准确选择对安全销的限制条件,在10%的安全销已经通过测试的条件下, 即使两个随机选取的安全销也几乎不可能超出限制条件。我推测,这样的限制条件可能是比实际应用中安全销需要满足的限制条件更窄的范围。
我不想为了评估快速排序算法的平均性能而进行这样的统计分析。即使我有信心可以正确地进行类似的统计分析,将来可能出现的规范或者测试程序的改变, 都会使这样的分析无效。同时,在以快速排序为例的算法,和以安全销为例的机械设备之间有一个重要区别,那就是,有时为了达到某些目的,算法的输入会被设计 的非常复杂。比如在Doug McIlroy 1999年写的论文中,详细描述了怎样构造快速排序的输入,使得算法对于n个元素的排序时间达到O(n2)。在这样的情况下,快速算法就与描述不符了吗?如果是这样,那么就很难看见我们现在对快速算法的应用了。
使得这样的性能测试问题简单化的一个方法是采用白盒测试的方法。白盒测试的方法利用了已知程序实现细节这一优势。下周,我会详细介绍这样的测试技术。