Thursday 4 November 2010

Using RAII idiom for profiling


The RAII is a well-known C++ idiom. The idea is simple and great at the same time. Two things in life are certain: birth and death. If you are wondering about taxes, please don't forget that I am Italian and in my country taxes are not mandatory.
In object oriented world birth means constructor. The constructor is the first function called when we create it, so it is a wonderful place to allocate a resource. Death means destructor, when a object goes out of scope the destructor is always called (of course if you leak the resource the destructor is not called). That's great, the destructor is the perfect place to release the memory.
Check this link for more information on RAII. What I am going to show you is a very simple class that implement this idiom to get profiling information.

Profiling
If you need to make your application go faster you need to know where your application is actually performing worst. DON'T SPECULATE! The developer are not good to guess where an application is going slow, if they were good the there wouldn't be slow applications.
Profiling is tedious, you need to put extra debug output everywhere, check the result and at the end remove them from the code (of course you can use a profiling tool, but sometimes the learning curve is so big that it is better using trivial way).
The RAII idiom will rescue us, have a look at this class:
  1. #ifndef _PROFILER_H_
  2. #define _PROFILER_H_
  3. #include <string>
  4. #include <iostream>
  5. #include <sys/time.h>
  6. using std::string;
  7. using std::ostream;
  8. namespace BitingCpp
  9. {
  10.     class Profiler
  11.     {
  12.         public:
  13.             Profiler(string what,
  14.                      ostream& out = std::cout,
  15.                      bool verbose = false);
  16.             ~Profiler(); /* throw() */
  17.         private:
  18.             Profiler(const Profiler& rhs);
  19.             Profiler& operator=(const Profiler& rhs);
  20.             string what_;
  21.             timeval start_;
  22.             timeval end_;
  23.             ostream& out_;
  24.             bool verbose_;
  25.     };
  26. } //namespace BitingCpp
  27. #endif

As you can see the class provides only constructor and destructor. The copies are not allowed. There are some private member and this is the implementation file:

  1. #include "Profiler.h"
  2. using std::endl;
  3. namespace BitingCpp
  4. {
  5.     Profiler::Profiler(string what,ostream& out,
  6.                                 bool verbose) :
  7.                                 what_(what), start_(),
  8.                                 end_(),out_(out),verbose_(verbose)
  9.     {
  10.         gettimeofday(&start_,0);
  11.         if (verbose_)
  12.             out_ << "Begin of " << what_ << " at " << start_.tv_sec
  13.                  << " s " << start_.tv_usec << " us" << endl;
  14.     }
  15.        
  16.     Profiler::~Profiler() /* throw() */
  17.     {
  18.         try
  19.         {
  20.             gettimeofday(&end_,0);
  21.             if (verbose_)
  22.                 out_ << "End of " << what_ << " at " << end_.tv_sec
  23.                      << " s " << end_.tv_usec << " us" << endl;
  24.            
  25.             double timems =
  26.               ((static_cast<double>(end_.tv_sec - start_.tv_sec))
  27.               *1000.0 + ((end_.tv_usec - start_.tv_usec)/1000.0));
  28.             out_ << what_ << " running time: "
  29.                  << timems  << " ms " << endl;
  30.         }
  31.         catch(...) {} // to avoid core dump during the stack unwind!
  32.     }
  33. } //namespace BitingCpp
The constructor stores the timestamp of creation (and print an optional statement) and the destructor makes a simple calculation and print the result.

This small program shows how to use this class:


  1. #include <iostream>
  2. #include "Profiler.h"
  3. int main (int argc, char *argv[])
  4. {
  5.     BitingCpp::Profiler mainProfile(__FUNCTION__);
  6.    
  7.     BitingCpp::Profiler* justFirstLongLoop =
  8.        new BitingCpp::Profiler("Just First Long Loop");
  9.    
  10.     int j = 0;
  11.     for (int i=0; i < 10000000; i++)
  12.         j += i*i;
  13.        
  14.     delete justFirstLongLoop; // trigger the deconstructor
  15.     BitingCpp::Profiler anotherBigLoop("Another Big Loop");
  16.     j = 0;
  17.     for (int i=0; i < 10000000; i++)
  18.         j += i*i;
  19.    
  20.    
  21.     return 0; // anotherBigLoop goes out of
  22. }             // scope first and then mainProfile



The output of this program is something like that:
Just First Long Loop running time: 52.852 ms
Another Big Loop running time: 52.435 ms
main running time: 105.829 ms
Now it is clear why the copies are not allowed, what does copying a Profiler object mean? The Profiler keeps track of its date of birth (actually a timeval structure). If I copy a Profiler object which date of birth should the new object have? The same of the first one or the time stamp of the copy operation? Too many question and furthermore I cannot see the point to make a copy of a Profiler object in the first place.
Note: the code is working on UNIX platform. You can easily change the implementation file to use Windows functions. __FUNCTION__ macro should be pretty much cross platform, I don't think it is already in the standard, but I am sure is widely supported.

No comments:

Post a Comment