C++性能测试工具简介

在软件开发过程中,性能测试是一个不可或缺的环节。它可以帮助识别代码中的性能瓶颈,从而进行优化。然而,使用复杂的性能分析工具可能会让这个过程变得繁琐。本文将介绍一种简单易用的性能测试工具,适用于C++应用程序。

简单的性能测试需求

有时候,只需要一个简单的性能测试工具来满足基本需求。例如,可能想知道ABC模块中哪部分代码执行时间最长,或者比较Xyz算法和Zyx算法的执行时间。在这种情况下,可以使用自定义的性能测试代码,而不必设置复杂的性能分析工具。

性能测试工具的基本要求

希望实现以下功能:

  1. 测量代码中任何函数的执行时间,甚至是例程的一部分。
  2. 添加到例程中的性能测试代码应该非常简单,最好是只添加一行额外的代码。
  3. 应该有一个标志来全局启用或禁用性能测试

现有的性能测试库

虽然有一些现成的性能测试库,如google/benchmark、Celero和Nonius,但这些库功能强大,可能不适合小型项目或快速实验。本文介绍的方法更适合这些场景。

性能测试计时器

性能测试的核心是一个好的计时器。以下是一些可用的计时器选项:

  1. RDTSC指令:返回自重置以来的CPU周期数,64位变量。
  2. Windows上的高性能计时器:提供最高可能的精度(<1us)。
  3. GetTickCount:10到16毫秒的分辨率。
  4. timeGetTime:使用系统时钟,分辨率与GetTickCount相同,但可以通过timeBeginPeriod增加到1ms。
  5. std::chrono:STL库中的计时器。

应该使用std::high_resolution_clock,但在VS2013中可能无法正常工作。在VS2015中已经修复。如果使用的是最新的编译器/库,那么std::chrono应该可以正常工作。如果使用的是旧工具,那么最好再次检查。

输出结果

在简单场景中,可能只需要使用printf/cout输出结果。另一种选择是直接记录到某个日志文件或使用Debug View。

性能测试的成本

测量某些效果可能会改变结果。性能测试代码对经过时间的影响有多大?如果它相对于测量的代码占用了相当长的时间,可能需要以某种方式延迟这个过程。

解决方案

一个简单的解决方案是: void longFunction() { SIMPLEPERF_FUNCSTART; SIMPLEPERF_START("loop"); for (int i = 0; i < 10; ++i) { SIMPLEPERF_SCOPED("inside loop"); //::Sleep(10); internalCall(); } SIMPLEPERF_END; } 程序结束时,将显示: main : 14837.797000 longFunction : 0.120000 loop : 0.109000 inside loop : 0.018000 internalCall : 0.008000 inside loop : 0.011000 internalCall : 0.009000 ... inside loop : 0.005000 internalCall : 0.002000 shortMethod : 15.226000 loop : 15.222000

可以使用以下三个基本宏:

  1. SIMPLEPERF_FUNCSTART:只需将其放在函数/方法的开头。它将显示函数的名称,并打印执行时间。
  2. SIMPLEPERF_SCOPED(str):将其放在作用域的开头。
  3. SIMPLEPERF_START(str):将其放在函数内部,作为一个自定义标记,其中没有打开作用域。
  4. SIMPLEPERF_END:需要关闭SIMPLEPERF_START。

此外,代码支持两种模式:

  1. Immediate:在获得经过时间后立即打印。打印可能会影响一些性能。
  2. Retained:将数据收集起来,以便在程序结束时显示。

在保留模式下,可以调用:

  1. SIMPLEPERF_REPORTALL:显示当前数据。
  2. SIMPLEPERF_REPORTALL_ATEXIT:在main()完成后显示数据。实际上可以在程序中的任何时候调用。

需要将#define SIMPLEPERF_SHOWIMMEDIATE设置为true以使用保留模式。

问题

整个计时器可能在多核、多线程代码中不起作用,因为它不使用任何临界部分来保护共享数据,也不关心代码正在运行的线程。如果需要一个更高级的计时器,那么可能对Preshing on Programming上的文章感兴趣:A C++ Profiling Module for Multithreaded APIs。

实现细节

计时器的核心思想是使用析构函数来收集数据。这样,当某个计时器对象超出范围时,就会得到数据。这对于整个函数/显式作用域非常方便。

{ // scope start my_perf_timer t; }

在基本的即时形式中,计时器只是在构造函数中保存时间(使用QueryPerformanceCounter),然后在析构函数中测量结束时间并将其打印到输出。

在保留模式下,还需要存储数据以备将来使用。简单地创建了一个静态向量,在构造函数中添加一个新条目,然后在析构函数中填充最终时间。还注意缩进,以便输出看起来很漂亮。

在仓库中,还有一个头文件版本(有点简化,只使用即时模式):see SimplePerfTimerHeaderOnly.h。

待办事项

在打印数据时添加文件/行信息? 使用std::chrono for VS2015/GCC版本

本文介绍了一个方便的性能计时器。如果只需要检查代码/系统执行时间,只需包含一个头文件(+添加相关的.cpp文件),并在分析的地方使用SIMPLEPERF_FUNCSTART或SIMPLEPERF_START(str)/END。最终输出应该可以帮助找到热点...而无需使用高级工具/机械。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485