测试速度

测试速度

测试程序的速度是优化工作的重要组成部分

有许多分析器可以用于查找热点和测量程序的总体性能,但是对于某些大部分时间花费在等待用户输入or磁盘文件读取的程序,分析器很难测量。

热点确定后,可以将热点代码单独提取出来测量性能,通过多次调用关键函数,并将每次运行的时间保存,可以在测试后输出。

函数第一次调用所花费的时间通常要高于随后的时间,因为第一次调用时代码以及数据都还未被缓存。因此,可以这么说,函数第一次调用对应该关键代码的最坏情况,而随后的调用对应代码运行的最佳情况。 具体采信哪个值取决于哪种情况更接近真实情况。若优化侧重于CPU效率,那么就应该考虑优化最佳情况下的程序运行时间,而若优化侧重于提高缓存效率,那么就应该考虑通过合理安排数据顺序优化最差情况下的运行时间

测试中,为了避免任务切换,可以在测试前增加测量任务的线程优先级,并在测试后再将其恢复为正常。

另外,为了避免CPU频率的动态变化,一般是工作负荷大时时钟频率增加,工作负荷小时时钟频率减小(为了省电)这会使测量不够准确,可以做以下尝试:

  1. 测试之前给CPU一些计算密集型工作以预热CPU
  2. 禁用BIOS设置中的省电选项
  3. 在Intel CPU上,使用内核时钟计数器

使用性能监视器计数器

许多CPU都有一个内置的测试特性,称为是性能监视器计数器,可以用来计数某些事件,比如执行的机器指令数量、缓存丢失、分支预测错误等。

CPU厂商会提供各自的CPU分析工具,intel的叫VTune, AMD的叫CodeAnalyst

intel处理器中,有一个特别有用的性能监视器叫核心时钟周期计数器,核心时钟周期计数器是按照CPU核心运行的实际时钟频率而不是外部时钟计算时钟周期,因此测试代码时,用这个可以避免时钟周期上升or下降的问题。

可以在程序中设置一个开关,便于关闭在非测试情况下的计数器读取,若性能监视器计数器被禁用时,对他们的读取会导致程序崩溃。

单元测试的陷阱

软件开发中,通常单独测试每个函数or类,此类单元测试对验证、优化函数功能非常必要,在CPU效率方面测试问题也不大,但是由于缓存的问题,单独拎出来测试与放到原来环境中是差距很大的。 因为我们单元测试时,程序代码和内存总量可能小于缓存大小,不存在任何竞争问题。而在最终程序中,代码缓存和数据缓存很有可能是关键资源。因此,由于当前CPU的速度很快,很有可能时间复杂度更高的但缓存占用更少的版本实际运行速度更快。(这说明,实际场景中,空间不一定能换到时间,可能会反而更差)

例如:大循环展开是否有利的判定不能单单依赖单元测试而不考虑实际缓存效果。

通过链接器的“生成映射文件选项”,可以查看连接映射or汇编代码列表来计算函数的内存占用。

代码缓存、数据缓存乃至于分支目标缓存都很重要,同时函数跳转、调用、分支数量也是影响程序性能的关键因素

一个实际的性能测试,不应该仅仅包括单个函数或者热点,还应该包括关键函数和热点的最内层循环,同时也应该使用真实数据来测试,借此获得更真实的分支预测结果。性能方面的度量不应该包括输入输出,用于文件输入和输出的时间应该分别测量。

最差条件测试

多数性能测试可能都是在最佳条件下进行的,消除了所有干扰的影响,资源充足、缓存充足,最佳条件下蹲测试结果可靠且可重复。

但是,某些情况下我们也应该关注最差情况下的结果,平均意义下的快对用户来说可能难以感受到,但是某次用户操作后,过了很久才响应,这种最差情况会极大的影响用户感受,因此若想保证最坏响应时间,应当且有必要进行最差情况的测试。

下列措施或许有利于测试最坏情况的性能

  1. 第一次激活程序的某部分时,由于代码延迟加载、缓存不命中以及分支预测错误等,可能会比随后的调用慢
  2. 测试整个软件包,包括所有的运行时库和框架,而不是单独测试单个函数,在软件包不同部分间切换,以增加程序代码的某些部分未缓存甚至是被交换到磁盘的可能性。
  3. 遗留网络资源和服务器的软件应该在流量比较大的网络和服务器上测试,而不是专用的测试服务器
  4. 使用包含大量数据的大型数据文件和数据库
  5. 使用CPU速度慢、RAM不足,安装大量无关软件,运行大量后台进程,磁盘速度慢且碎片化的旧计算机
  6. 使用不同品牌,不同性能的PCU,不同类型的显卡等测试
  7. 使用杀毒程序扫描所有的文件访问
  8. 同时运行多个进程或者线程,如果支持超线程,尝试在同一个处理器内核中同时运行两个线程
  9. 尝试分配比现有内存更多的RAM,以便强制将内存交换到磁盘
  10. 通过使用最内存循环中的代码或者数据来触发缓存不命中,或者可以主动的使缓存失效,例如指令集函数_mm_clflush
  11. 使用比正常情况随机性更高的数据,以便引发分支错误预测。