Go Down

Topic: Class to help debug how long code takes (Read 1 time) previous topic - next topic

Nick Gammon

May 11, 2011, 12:34 am Last Edit: May 11, 2011, 11:14 pm by Nick Gammon Reason: 1
The "ProfileTimer" class below is adapted from code I used to use when developing Windows programs.  It solves in a neat way the general problem of wanting to time bits of code in a simple way.

With the class installed (eg. from a header file) you can time a function (or part of a function) with a single line, like this:

Quote
void readSensors ()
  {
  ProfileTimer t ("readSensors");

  a = analogRead (A0);
  b = analogRead (A1);
  }


What that does is the ProfileTimer constructor notes the current time. And then when the destructor is reached at the end of the function it displays the time in microseconds. To get rid of it you just comment out or delete a single line.

Example output:

Code: [Select]
Start     : readSensors
Time taken: readSensors = 4248 microseconds.


You can time part of a function by simply forcing the ProfileTimer class to have local scope like this:

Code: [Select]
... early part of function here
{
    ProfileTimer t1 ("multiple reads");
    for (int i = A0; i <= A5; i++)
      analogRead (i);
  }  // end timed bit of code
... rest of function here



Class and example code below. You could easily change it to use millis rather than micros if you prefer. Millis has less resolution but can time a longer interval (micros() can time about 70 minutes, millis() can time about 50 days).

Code: [Select]
// ProfileTimer class
// Author: Nick Gammon
// Date: 11th May 2011

class ProfileTimer
{
  unsigned long start_;
  const char * sReason_;

public:

  // constructor remembers time it was constructed
  ProfileTimer (const char * reason) :
  sReason_ (reason)
  {
    Serial.print ("Start     : ");
    Serial.println (sReason_);
    start_ = micros ();
  }

  // destructor gets current time, displays difference
  ~ProfileTimer ()
  {
    unsigned long interval = micros () - start_;
    Serial.print ("Time taken: ");
    Serial.print (sReason_);
    Serial.print (" = ");
    Serial.print (interval);
    Serial.println (" uS.");
  }
};    // end of class ProfileTimer


void setup ()
{
  Serial.begin (115200);
}  // end setup

void loop ()
{
  delay (500);

  ProfileTimer t ("analog read");
  analogRead (A1);

  {
    ProfileTimer t1 ("multiple reads");
    for (int i = A0; i <= A5; i++)
      analogRead (i);
  }  // end timed bit of code

}  // end loop


You can have multiple nested timers as in the example. This can also be used to indicate function coverage (that is, what functions get called in what order). You could, of course, change Serial.print to whatever your specific requirements were for reporting the information.

Example output:

Code: [Select]
Start     : analog read
Start     : multiple reads
Time taken: multiple reads = 5244 uS.
Time taken: analog read = 11396 uS.

Nick Gammon

#1
May 11, 2011, 03:57 am Last Edit: May 11, 2011, 11:15 pm by Nick Gammon Reason: 1
The class is now a library which you can download from here:

http://www.gammon.com.au/Arduino/ProfileTimer.zip (4 Kb)

The example code can now be simplified to:

Code: [Select]

#include <ProfileTimer.h>

void setup ()
{
 Serial.begin (115200);
}  // end setup

void readSensors ()
 {
ProfileTimer t ("readSensors");

 int a = analogRead (A0);
 int b = analogRead (A1);
 }
 
void loop ()
{
 delay (500);

 ProfileTimer t ("analog read");
 analogRead (A1);

 {
   ProfileTimer t1 ("multiple reads");
   for (int i = A0; i <= A5; i++)
     analogRead (i);
 }  // end timed bit of code

}  // end loop

westfw

Neat.  Does some part of C++ guarantee that the object is constructed (and later destroyed) even if it's not otherwise used ?

Aeturnalus


Neat.  Does some part of C++ guarantee that the object is constructed (and later destroyed) even if it's not otherwise used ?


Yes, C++ scoping will call the destructor of any local objects when the closing bracket is reached, and it's constructed explicitly: the constructor is called here: 
Code: [Select]
timer t ("readSensors");

Nick Gammon

#4
May 11, 2011, 11:37 am Last Edit: May 11, 2011, 11:16 pm by Nick Gammon Reason: 1
I got an idea from elsewhere in this forum. If you want to time a function, rather than retyping the function name you can use __PRETTY_FUNCTION__ which returns the current function name and its arguments:

Code: [Select]
void readSensors ()
 {
 ProfileTimer t (__PRETTY_FUNCTION__);

 //  do something lengthy here
 
 }


Output looks like:

Code: [Select]
Start     : void readSensors()
Time taken: void readSensors() = 5440 uS.


So you can then make a define to let you time any function:

Code: [Select]
#define PROFILE_FUNCTION  ProfileTimer t (__PRETTY_FUNCTION__)

And use it like this:

Code: [Select]
void readSensors ()
 {
 PROFILE_FUNCTION;

 //  do something lengthy here
 
 }


Since it picks up the function name automatically, you just bang in PROFILE_FUNCTION wherever you want to time something.

This define is now in the ProfileTimer.h file.

Nick Gammon

Modified above posts and downloadable library to rename timer to Timer, to be consistent with other libraries.

AWOL

Wouldn't it be better to note the start time after printing?
"Pete, it's a fool looks for logic in the chambers of the human heart." Ulysses Everett McGill.
Do not send technical questions via personal messaging - they will be ignored.

Nick Gammon

#7
May 11, 2011, 12:56 pm Last Edit: May 11, 2011, 11:16 pm by Nick Gammon Reason: 1
I didn't understand the question, sorry.

Or maybe ...

Did you mean change:

Code: [Select]
// constructor remembers time it was constructed
ProfileTimer (const char * s) : sReason (s)
 {
   start = micros ();
   Serial.print ("Start     : ");
   Serial.println (sReason);
 };


to:

Code: [Select]
// constructor remembers time it was constructed
ProfileTimer (const char * s) : sReason (s)
 {
   Serial.print ("Start     : ");
   Serial.println (sReason);
   start = micros ();
 };


?

In which case, yes I think you are very much right.

Nick Gammon

Amended original post, and downloadable library, to incorporate your suggestion.

PaulS

I have another problem with the name of the class. The Arduino has a number of timers, and there are libraries for manipulating these timers, with Timer in the name.

Your class, while quite nice, is a bit confusing, with Timer as it's name. Perhaps the class should be called FunctionDuration or IntervalDuration, or even Duration, to make it clear that it is NOT a new class for manipulating one of the timers.

Coding Badly


AWOL

#11
May 11, 2011, 08:11 pm Last Edit: May 11, 2011, 09:45 pm by AWOL Reason: 1
Quote
I suggest "ProfileTimer".

[ + ]
"Pete, it's a fool looks for logic in the chambers of the human heart." Ulysses Everett McGill.
Do not send technical questions via personal messaging - they will be ignored.

bigengineer

Wow, haven't been here for a long time. Start browsing for a clue to figure out how long a function takes and crash right into this.

Thanks, really useful!

Nick Gammon

Posts above, and downloadable file, amended to rename it to ProfileTimer. Added the define PROFILE_FUNCTION to the header file. This makes it simple to profile for debugging.

Thus in your code:

Code: [Select]
void readSensors ()
  {
  PROFILE_FUNCTION;    // shows when function called

  //  do something lengthy here
 
  }


However once debugged you just put this at the start of your main code:

Code: [Select]
#undef PROFILE_FUNCTION
#define PROFILE_FUNCTION


That turns the profiling into a no-op.

Nick Gammon


Wouldn't it be better to note the start time after printing?


It would also be better to calculate the elapsed time before starting to print what it is (the string part). So I amended it to calculate the difference first, and then start printing what it is.

Go Up