多个性能计时器
计时您的代码的多个部分并显示累积结果。
引言
本文包含一个名为 MultiTimer.h 的文件,该文件可让您轻松计时代码的各个部分。它会在多次调用时累积时间,并生成一个输出摘要,其中包含每个计时器的总计和调用次数。这里及其他地方有许多类似的示例,但我认为添加本文仍然值得,因为我认为我的计时器类添加了一些额外的功能并且非常易于使用。
示例
void testfun() { printf("Hello World!\n"); } int main(int argc, char* argv[]) { for (int i = 0; i < 10; i++) testfun(); return 0; }
这并不是要计时代码的最佳示例,但它是一个很好的示例,说明了如何使用计时代码。请参阅下面的使用指南,了解如何计时代码的想法。
添加计时代码
添加计时代码需要以下步骤:
- 添加
#include "MultiTimer.h"
- 向要计时代码的代码中添加计时宏
- 调用
TIMER_DUMP
或类似的宏来显示结果
添加了一些测试计时代码后,上面的示例看起来像这样:
#include "MultiTimer.h" void testfun() { TIME_LINE(printf("Hello World!\n");) } int main(int argc, char* argv[]) { { TIME_SCOPE("total test") for (int i = 0; i < 10; i++) TIME_FUN(testfun()) } TIMER_DUMP("c:\\times.txt", SORT_CALLORDER) return 0; }
这将产生以下输出文件:
-----------------------------------------------------------------
Timings at 10:41:56 Friday, January 03, 2003
-----------------------------------------------------------------
total test took 606.80 micro secs (called 1 time)
testfun() took 591.42 micro secs (called 10 times)
printf("Hello World!\n"); took 561.52 micro secs (called 10 times)
结果相当直接。如果您下载了演示代码,您将看到更详细的代码,它分解了 testfun()
调用所花费的时间。这是该代码的示例输出:
Total: 668.20 micro secs
Minimum: 39.39 micro secs
Maximum: 302.55 micro secs
Average: 66.82 micro secs
-----------------------------------------------------------------
testfun() at 10:22:06 Friday, January 03, 2003
-----------------------------------------------------------------
Sample 1 = 302.55 micro secs
Sample 2 = 58.11 micro secs
Sample 3 = 55.87 micro secs
Sample 4 = 41.63 micro secs
Sample 5 = 42.46 micro secs
Sample 6 = 41.07 micro secs
Sample 7 = 42.46 micro secs
Sample 8 = 39.39 micro secs
Sample 9 = 50.29 micro secs
Sample 10 = 43.02 micro secs
您可以从上面看出,第一个 printf
在 Windows 2000 上花费的时间要长得多,我猜这与某些初始化有关。
宏
从上面的示例中,您可以看到此计时代码在很大程度上由宏驱动。以下是使用的宏列表:
TIMER_SETUP
这需要包含在项目中的一个 .cpp 文件中,并且处于全局作用域。它为计时代码设置存储。
TIME_SCOPE(name)
将其放置在您的代码中,以从该点计时到当前作用域的结束。name
是一个字符串,用于输出中标识这些时间。
TIME_FUN(fun())
用此宏替换函数调用 fun()
以计时该调用。
TIME_LINE(line)
用此宏包装任何行以对其进行计时。避免在该行中声明任何变量,因为此宏将该行放入一个单独的作用域。
TIMER_START(name) 和 TIMER_STOP(name)
这些宏允许您手动启动和停止计时器。请参阅演示示例。
TIMER_DUMP(dest, sort)
转储当前时间。如果 dest
是文件名,则输出将写入该文件。否则,它可以是:
OUT_MSGBOX
- 在消息框中显示输出OUT_TRACE
- 将输出发送到跟踪窗口OUT_CONSOLE
- 将输出发送到stdout
输出可以按 3 种方式排序:
SORT_TIMES
- 最长的时间显示在最前面,直到最短SORT_CALLORDER
- 时间按计时器调用顺序显示SORT_NAMES
- 输出按字母顺序排序
TIMER_DUMP_FILE(dest, sort, mode)
这允许 3 个额外的模式值:
FILE_NEW
- 每次创建新文件FILE_APPEND
- 将每次输出添加到文件末尾FILE_PREPEND
- 将每次输出添加到文件开头
如果您想比较几次测试运行,这很有用。
TIMER_DUMP_CSV(dest, sort, mode)
这与 TIMER_DUMP_FILE
相同,但输出是逗号分隔格式。
TIMER_DUMP_SAMPLES*(timer, ...)
TIMER_DUMP_SAMPLES
宏会转储指定计时器的所有样本,这可以是它的名称或 CHiFreqTimer
对象。请参阅演示示例。
有 3 个 TIMER_DUMP_SAMPLES
宏,它们对应于 TIMER_DUMP
宏,只是没有 sort 参数 - 样本按升序转储。
GET_TIMER(name)
返回计时器 name
的 CHiFreqTimer
对象。然后可以使用进一步的统计信息,请参阅演示示例。
T2S(time)
将从 CHiFreqTimer
调用返回的时间转换为有意义的字符串,请参阅演示示例。
CLEAR_TIMERS
移除所有计时器。
禁用计时代码
在 MultiTimer.h 的顶部有一个 #define USE_TIMERS
。注释掉它将编译掉您可能添加的任何计时代码。
计时多个文件
您可以将计时代码放在多个文件中。每个文件都需要包含 multitimer.h,除了第一个文件外,所有文件都需要在 #include
之前添加以下行:
#define ANOTHER_TIMER_FILE
时钟滴答
了解一段代码花费了多少个时钟滴答有时很有用。我添加了一些宏,可以将花费的时间转换为时钟周期,首先您必须取消注释 multitimer.h 文件顶部的 #define _CLOCKTICKS
行。它们是:
T2C(time)
将时间转换为包含所花费时钟周期的字符串。
T2S_WITHCYCLES(time)
将时间转换为包含时间和周期数的字符串。
CLOCK_SPEED
这是一个 int
,表示您的机器速度,例如 733。该数字的准确度在您的特定机器速度的 1 到 2 个以内。
使用指南
此类代码的目的是识别项目中的慢速部分。如果项目的一部分很慢,它应该有助于您缩小导致问题的代码范围。您应该避免计时非常快的语句 - 计时可能不准确。也不要计时所有代码,而是专注于需要加速的慢速区域。了解特定行或函数被调用了多少次也很有帮助,因为这通常很难仅从代码中找出。
理想情况下,应该始终使用发行版代码来获得准确的计时,调试代码中的额外检查和缺乏优化可能会导致误导性的结果。其他正在运行的进程也会影响时间,例如杀毒软件可能会扭曲文件访问时间。
准确性和计时开销
用于计时文件的任何代码本身也会花费一些时间。此代码试图对此进行补偿,但如果计时器和/或计时调用数量非常大,可能会导致精度略有损失。
QueryPerformanceCounter()
调用用于计时。这仅在 Pentium 处理器上受支持。如果您在旧机器上运行,升级可能会加快速度……
变更历史
- 6/11/02
- 对 MultiTimer.h 文件的通用改进
- 添加了
TIMER_DUMP_FILE
宏
- 7/11/02
- 添加了
TIMER_START
+TIMER_STOP
宏 - 添加了
USE_TIMERS #define
- 添加了
- 3/1/03
- 整理了
TIMER_START
和TIMER_STOP
手动计时器 - 添加了
TIMER_DUMP_CSV
宏 - 添加了
TIMER_DUMP_SAMPLES
、TIMER_DUMP_SAMPLES_FILE
和TIMER_DUMP_SAMPLES_CSV
宏 - 添加了时钟滴答宏
- 移除了
TIMER_SETUP
宏
- 整理了