CProfile - 一个用于代码性能分析和跟踪的简单类






4.33/5 (9投票s)
这个简单的类允许剖析代码段,并可选择以不同格式打印经过的时间和跟踪字符串。
引言
我编写了CProfiler
类来继续学习Win32 API和C++编程。基本思想是以一种非常简单的方式实现一个简单的类来执行代码和函数剖析功能。我查阅了Platform SDK,并在Win32 API中找到了QueryPerformanceCounter
和QueryPerformanceFrequency
函数。我从这里开始,完成了一个粗糙但功能齐全的类。编程很有趣(至少对我这个业余爱好者和新手C++程序员来说)。
此代码是用Visual C++ 7.0 (2002) 编写(且仅在此版本测试)的。
Using the Code
CProfiler
类的声明如下:
class CProfiler
{
public:
CProfiler(void);
~CProfiler(void);
void ProfileStart(LOGTYPE logtype = LOGNONE);
__int64 ProfileEnd (char* TraceStr = "");
double SecsFromTicks ( __int64 ticks);
DWORD GetLastRetVal(void);
private:
LARGE_INTEGER m_QPFrequency; // ticks/sec resolution
LARGE_INTEGER m_StartCounter; // start time
LARGE_INTEGER m_EndCounter; // finish time
__int64 m_ElapsedTime; // elapsed time
DWORD m_Retval; // return value for API functions
LOGTYPE m_LogType; // logging type
};
好了,让我们从查看类声明开始,了解如何使用它。
首先,您当然需要创建一个CProfiler
类型的对象。如上所示,构造函数不接受任何参数;它执行以下初始化任务:使用ZeroMemory
API清除所有LARGE_INTEGER
联合,并通过QueryPerformanceFrequency
API获取本地机器的高分辨率计数器频率。此频率存储在m_QPFrequency
数据成员中。
构造函数、ProfileStart
函数和ProfileEnd
函数使用m_Retval
private
数据成员来存储API的返回值。您可以在对象构造、调用ProfileStart
或ProfileEnd
后,使用成员函数GetLastRetVal
检查API是否失败。
成功构造对象后,您就可以开始使用ProfileStart
和ProfileEnd
函数了,它们是计时器的“播放/停止”按钮。
如果查看这些函数,它们的定义如下:
void ProfileStart(LOGTYPE logtype = LOGNONE); // starts profiling
__int64 ProfileEnd (char* TraceStr = ""); // end profiling
请注意,我定义了ProfileStart
函数来接受一个LOGTYPE
类型的参数,它默认为LOGNONE
……这是什么?它是一个枚举,我用它来指示使用的调试日志记录的类型。enum
声明如下:
enum LOGTYPE { LOGNONE, LOGTICKS, LOGSECS, LOGALL, LOGMSGBOX };
有了这个枚举,我就能确保只有这五个值可以作为参数使用。
让我们解释一下ProfileStart
函数不同的日志记录类型:LOGNONE
不记录任何内容;LOGTICKS
以时钟滴答数为单位记录经过的时间;LOGSEGS
以秒为单位记录经过的时间;LOGALL
以两种格式记录经过的时间;最后,LOGMSGBOX
也以两种格式显示时间,并显示一个对话框(就像本文顶部的那个对话框一样……)。请注意,LOGTICKS、LOGSECS
和LOGALL
参数使用OutputDebugString
函数写入VC++调试器结果窗口。
CProfiler
类的一般使用模式可以用以下代码表示:
#include <iostream>
#include "profiler.h"
using namespace std;
int main(void)
{
// create object
CProfiler cprof;
int i;
float f;
__int64 ptime;
// start profiling (no debug output)
cprof.ProfileStart(LOGNONE);
for (i = 0, f = 0.0; i < 10000; i++, f+ = 0.5)
cout<< static_cast<float>((i)*f/2) << endl;
// end profile here and save the result
ptime = cprof.ProfileEnd();
cout << "Function time: " << ptime << " ticks ("
<< cprof.SecsFromTicks(ptime) << " seconds)" << endl;
system("pause");
return 0;
}
在上面的代码中,我们剖析了一个相当愚蠢且无意义的函数,仅用于演示目的:一个计算(i)*f/2
的函数,其中i
从0到10000,f
是一个float
,从0.0到500.0。这里我们不使用任何调试日志记录,正如ProfileStart(LOGNONE)
行所示。
接下来,我们看到ptime = ProfileEnd()
。此函数结束剖析并以64位有符号整数(__int64
)形式返回经过的时间(时钟滴答数)。最后,我们使用此值来表示经过的时间(ptime
)以时钟滴答数计,以及以秒计。后者可以通过SecsFromTicks
成员函数获得,并传入一个表示经过滴答数的__int64
值。这就是SecsFromTicks(ptime)
,它返回一个double
。
跟踪
为了跟踪代码的执行位置,我们还可以使用ProfileEnd
成员函数中的char*
参数输出一个string
到调试日志记录中。例如,您可以这样写:
float MyComplexCalc (float x, float y, float z)
{
... // complex function code ;)
return fResult;
}
// profile the MyComplexCalc function
int main(void)
{
CProfile prof;
ProfileStart (LOGTICKS);
double fcalc = MyComplexCalc (7.0, 0.6, 8.2);
ProfileEnd ("Mycomplex calc function finished...");
}
查看ProfileEnd
函数。现在该函数的参数是一个string
,用于跟踪您在代码中的位置、您正在进行的函数剖析,或者您想与ProfileStart
成员函数中LogType
参数指定的调试输出日志一起输出的任何消息。
上面的代码将输出到VC++调试器的结果窗口(或像SysInternals DebugView这样的调试实用程序)。
日志模式
以下屏幕截图将展示一个示例,摘自上述示例代码,展示了每种日志模式的预期结果。屏幕截图来自DebugView
实用程序。在Visual C++调试器的输出窗口中,您将看到类似的结果。
- 使用上述代码进行剖析,但使用
LOGALL
日志类型 - 现在使用
LOGTICKS
… - 接下来,使用
LOGSEGS
… - 最后,花哨的(有用的?)
LOGMSGBOX
。
结论
这个类写起来很有趣,对我继续学习VC++技巧非常有帮助。请注意,我不是专业程序员,实际上,我正在大学学习社会科学,但我非常喜欢开发软件。
事实上,这段代码的目的是接收关于如何改进我的编码风格、避免错误等的批评和建议。如果有人觉得这个CProfiler
类有用(或作为更好类实现的基石),我将非常乐意听到评论。
历史
- 2003年5月2日 - V1.0首次发布