65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (9投票s)

2003年5月2日

CPOL

4分钟阅读

viewsIcon

66927

downloadIcon

719

这个简单的类允许剖析代码段,并可选择以不同格式打印经过的时间和跟踪字符串。

引言

我编写了CProfiler类来继续学习Win32 API和C++编程。基本思想是以一种非常简单的方式实现一个简单的类来执行代码和函数剖析功能。我查阅了Platform SDK,并在Win32 API中找到了QueryPerformanceCounterQueryPerformanceFrequency函数。我从这里开始,完成了一个粗糙但功能齐全的类。编程很有趣(至少对我这个业余爱好者和新手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的返回值。您可以在对象构造、调用ProfileStartProfileEnd后,使用成员函数GetLastRetVal检查API是否失败。

成功构造对象后,您就可以开始使用ProfileStartProfileEnd函数了,它们是计时器的“播放/停止”按钮。

如果查看这些函数,它们的定义如下:

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、LOGSECSLOGALL参数使用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++调试器的输出窗口中,您将看到类似的结果。

  1. 使用上述代码进行剖析,但使用LOGALL日志类型

  2. 现在使用LOGTICKS

  3. 接下来,使用LOGSEGS

  4. 最后,花哨的(有用的?)LOGMSGBOX

结论

这个类写起来很有趣,对我继续学习VC++技巧非常有帮助。请注意,我不是专业程序员,实际上,我正在大学学习社会科学,但我非常喜欢开发软件。

事实上,这段代码的目的是接收关于如何改进我的编码风格、避免错误等的批评和建议。如果有人觉得这个CProfiler类有用(或作为更好类实现的基石),我将非常乐意听到评论。

历史

  • 2003年5月2日 - V1.0首次发布
© . All rights reserved.