Profiling an application is necessary when you want to learn more about how it runs and where its bottlenecks are. A profiler is an analysis tool, which tracks the application while it is being executed. Although the capabilities of the profilers vary, graphically displaying the call cost and hit count--the average time elapsed in milliseconds for a function call, and the number of times a function is called, respectively-- are the most essential features profiling tools should have.
I use Microsoft Visual C++ 6.0 Professional Edition on Windows 2000 and Windows XP. Although this compiler has a profiler, it is a bit cumbersome. Moreover, analyzing the profiler data is not easy. Microsoft has provided the PROFILER.XLM macro to analyze the profiler data. However, I think it is also difficult to use. Consequently, I decided to develop my own simple profiler. The complete source code (and related files) for the profiler is available online.
My primary design goals are:
- Ease of use. Developers should not need to learn many things on the profiler. It should be as simple as possible. The profiler should be able to profile multithreaded applications.
- Charts. The call cost and hit count charts should be generated.
- Averaging. Statistically, it is desirable to collect as much profiler data as possible and then take an average of them.
I decided to develop a class to collect the profiler data and a tool to analyze them graphically with Microsoft Excel (I tested the tool with Microsoft Excel 2000 and Microsoft Excel 2003.) Obviously, I am not after precise measurements; I simply want to compare some numeric data. The CProfiler class is aimed at function level profiling; it doesn't provide any data on line level.
The tool is tightly coupled to Microsoft Excel. However, I developed the CProfiler class with portably in mind; the SData struct is a wrapper for any specific implementation.
CProfiler Class
Listing One shows this class. Note the static functions and data members.
struct SData; class CProfiler { public: CProfiler(wchar_t* pId); CProfiler(void* pAddress, wchar_t* pType, wchar_t* pOp, wchar_t* pId); ~CProfiler(); bool InitInstance(); void StopInstance(); static void InitClass(wchar_t* pDir, bool profiling); static void StopClass(); static void DeleteTemps(); static void ProcessData(); private: static bool IsDirOK(wchar_t* pDir); SData* m_start; FILE* m_file; bool m_failed; wchar_t* m_pId; static SData* s_frequency; static wchar_t s_dir[LEN_BUFFER]; static bool s_profiling; static bool s_failed; static bool s_stopped; static std::map<int,FILE*> s_files; static void* s_mutex; };
To simplify the usage, I developed the macros in Listing Two.
#define PROFILER_INITCLASS(dir) CProfiler::InitClass(L##dir, true) #define PROFILER_INITCLASS_CURRDIR CProfiler::InitClass(0, true) #define PROFILER_PROCESSDATA CProfiler::ProcessData() #define PROFILER_START(id) CProfiler profiler(L##id) #define PROFILER_STOP profiler.StopInstance()