Hello.
I have a simple task I find myselft doing repeatedly in Arduino projects, timing sections of code to see how long they take to run.
I've finally (after 5 years) got the hardware of an 8x8x8 ws2811 LED cube mounted safely where the cat won't destroy it and i intend to work on animations for it when I get the chance.
I would like not to repeat the mistakes of a previous project involving thousands of leds where my codebase got so hard to understand I would spend a significant amount of time every year trying to remember how it works exactly.
The code I will post I believe is easy to follow for a novice like me, and I'm happy to hear ideas from anyone regarding it's structure, implementation, typo's, language i use in this post to describe it... whatever.
I was aiming for a simple bit of code I could re-use in my arduino projects and decided on a some variables and functions encapsulated in a namespace.
1. Given I can't see it meaningfull to have more than one timer I decided not to use a class/object and went for namespace. Is that a correct thought, are there other options here that are better or worse?
2. I can imaging using these timers and turning them off when I want to test my code without the timers, maybe need Serial output for other things or possibly just to free up some cycles. Is my method of turnOn() turnOff() a good strategy here and am I right in thinking calls to blank functions are the same as no call at all after the compiler has optimised this out? I suppose I'm still wasting some memory on variables I'm not using. Is it a better practice just to put #ifdef #endif around my timer stuff?
3. Is there another mechanism than micros() that might give me better resolution or less overhead or less error.
I'll post the complete code here and put a link to github if people would like to update changes with a pull request. I'm also trying to start a habbit of using github as I can see a benefit of it for my previously mentioned large led project.
I have a simple task I find myselft doing repeatedly in Arduino projects, timing sections of code to see how long they take to run.
I've finally (after 5 years) got the hardware of an 8x8x8 ws2811 LED cube mounted safely where the cat won't destroy it and i intend to work on animations for it when I get the chance.
I would like not to repeat the mistakes of a previous project involving thousands of leds where my codebase got so hard to understand I would spend a significant amount of time every year trying to remember how it works exactly.
The code I will post I believe is easy to follow for a novice like me, and I'm happy to hear ideas from anyone regarding it's structure, implementation, typo's, language i use in this post to describe it... whatever.
I was aiming for a simple bit of code I could re-use in my arduino projects and decided on a some variables and functions encapsulated in a namespace.
1. Given I can't see it meaningfull to have more than one timer I decided not to use a class/object and went for namespace. Is that a correct thought, are there other options here that are better or worse?
2. I can imaging using these timers and turning them off when I want to test my code without the timers, maybe need Serial output for other things or possibly just to free up some cycles. Is my method of turnOn() turnOff() a good strategy here and am I right in thinking calls to blank functions are the same as no call at all after the compiler has optimised this out? I suppose I'm still wasting some memory on variables I'm not using. Is it a better practice just to put #ifdef #endif around my timer stuff?
3. Is there another mechanism than micros() that might give me better resolution or less overhead or less error.
I'll post the complete code here and put a link to github if people would like to update changes with a pull request. I'm also trying to start a habbit of using github as I can see a benefit of it for my previously mentioned large led project.
GitHub - gibbedy/codeTimer: Time execution time of sections of code in an arduino program
Time execution time of sections of code in an arduino program - gibbedy/codeTimer
github.com
Code:
/*
* On Arduino, I occasionally want to test how long a section or sections of code takes to execute.
* I do this because I often find I want to know how long some code blocks for or how much of every
* loop I am left with to complete a certain task. Establishing a hard limit on the length/complexity of
* things I might want to code.
* In order to not fill up my program with debug global variables to store the results of my tests I
* thought it would be better hide all the details in a namespace where I can time, store, and recall these
* times for multiple sections of code with minimal extra text in my source code.
*
* The following is my CodeTimer functions. These will let me record the runtime of sections of code and print the results
* to serial with a meaningfull name to allow easy understanding of how long some operations take in my program.
* I can recall the time for a given id and print the time out to the serial console to see what is going on.
*
* I put them in their own namespace so i can call these functions through that name and not have to worry about naming conflicts.
*
* If I don't turnOn() all these functions will be empty and do nothing, so I can easily enable and disable this with one
* line in the main program rather than having to put an #ifdef aroun every location where I use these functions.
* I assume the empty functions are filtered out at compilation so I end up with the same result as if there was no function
* calls at all?
*
* Usage would be:
* CodeTimer::turnOn(); //enable timers in setup
* CodeTimer::startTimer("description"); // put before the code you want to time
* CodeTimer::stopTimer("description"); // put after the code I want to time. Must have the same name as the start time
* CodeTimer::printResults(); //print to Serial the times of all the sections that have been timed.
*
*/
#ifndef CODETIMER_H
#define CODETIMER_H
namespace CodeTimer
{
bool enableTests = false; // Enable tests allows leaving timer starts and stops in your main code and disabling them with one change here
const int MAX_TEST_NAME = 15; // Maximum length of testname that is used to identify the section of code that is being timed
const int MAX_TESTS = 20; // Maximum number of unique tests that can be performed.
const int MAX_RECORDS = 100; // Maximum number of records for each test. When printing these will be averaged.
void turnOn()
{
enableTests = true;
}
void turnOff()
{
enableTests = false;
}
// Each test stores its results in this structure:
struct testResult
{
char testId[MAX_TEST_NAME]; // The name of the section of code under test
unsigned long runTime[MAX_RECORDS]; // The runtime, in microseconds, of the section of code is put in here
int recordNumber = 0; // Number of records currently stored for this testId. Used so I know when the runtime array is full.
unsigned long startTime; // Store the start time of the test currently being performed
bool recordsFull = false; // If i fill the runTime array i set this flag.
// This was added because when averaging I stop at recordNumber (an array that is not full)
// but if recordsFull is set I know the array is full (times are recorded over and over in array)
// represents an array of runTimes thatso when averging I know to ignore recordNumber and average the whole Array
};
testResult tests[MAX_TESTS]; //tests for up to MAX_TESTS are stored here
int numberOfTests = 0; // how many tests are currently in the tests array.
int checkRecordsForTest(const char recName[]); // Check if a record name is already in the tests array and return the index of it or -1 if not
unsigned long getAverage(int testsIndex); // get the average of all the runtimes for a given test by array index. Used by printTests function.
int checkRecordsForTest(const char recName[])
{
if(enableTests)
{
int recordIndex = 0;
for (; recordIndex < numberOfTests; recordIndex++) // For each section of code that is being tested
{
if (strcmp(tests[recordIndex].testId, recName) == 0) return recordIndex; //break out of loop if recName is found in the records
}
return -1; // If we got this far the loop was searched and no recName was found so return -1
// I can imagine more complex functions having common code that you would want to run regardless
// of whether a previous function return was made or not, and writing the function in a way so you
// have only one return function. By having one return function I would be helping avoid a situation
// where this common code was missed. Doing things that way would add a little overhead in this, and
// I think it is correct to say all cases. in this case It would add no benifit and an extra
// if check. It was drummed into me from java days. Why was that particularly important in java?
}
}
unsigned long getAverage(int testsIndex)
{
if(enableTests)
{
unsigned long sum = 0;
int maxIndex = tests[testsIndex].recordNumber;
if (tests[testsIndex].recordsFull) // The test located at 'testsIndex' is full (not on the first pass of filling the array)
{
maxIndex = MAX_RECORDS; // So override maxIndex to be the index of a full records array
}
for (int i = 0; i < maxIndex; i++)
{
sum += tests[testsIndex].runTime[i];
}
return sum / maxIndex;
}
}
// Record the start time of the test and give the record a name
void startTimer(const char recName[])
{
if(enableTests)
{
int startTime = micros();
int recordIndex = checkRecordsForTest(recName); //if the testname is already in the testResults then it returns the position of that test in the testResults array otherwise it returns -1
if(recordIndex >= 0) //record exists
{
tests[recordIndex].startTime = startTime; //Store the start time in the appropriate test record
}
else //record doesn't exist so create one
{
if (numberOfTests < MAX_TESTS) //check we havne't maxed out our number of sections of code to test
{
strncpy(tests[numberOfTests].testId,recName,MAX_TEST_NAME - 1); // set name for test by copying char string
tests[numberOfTests].testId[MAX_TEST_NAME - 1] = '\0'; //null terminate it. Only needed if I accidentally made the name longer than allowed by MAX_TESTNAME
tests[numberOfTests].startTime = startTime; //set the startTime we recorded at the start of this function.
numberOfTests++;
}
else
{
Serial.println("too many tests");
Serial.println("Increase MAX_TESTS");
}
}
}
}
// Stop the timer and specifiy an id to be used to store the result
void stopTimer(const char recName[])
{
if(enableTests)
{
unsigned long runTime = micros(); // for now just put current time in there until i find the start time.
int recordIndex = checkRecordsForTest(recName); //if the testname is already in the testResults then it returns the position of that test in the testResults array
if(recordIndex >= 0) //record exists
{
tests[recordIndex].runTime[tests[recordIndex].recordNumber] = runTime - tests[recordIndex].startTime; //Store the start time in the appropriate test record
tests[recordIndex].recordNumber++;
if (tests[recordIndex].recordNumber >= MAX_RECORDS)
{
tests[recordIndex].recordNumber = 0;
tests[recordIndex].recordsFull = true;
}
}
else //record doesn't exist so we have called a stop on a start that doesn't exist
{
Serial.print("Called a stop with an Id: ");
Serial.print(recName);
Serial.println (" that doesn't exist!");
}
}
}
void printResults()
{
if(enableTests)
{
Serial.print("CodeTimer results for ");
Serial.print(numberOfTests);
Serial.println(" tests");
for (int i = 0; i < numberOfTests; i++)
{
Serial.print(tests[i].testId);
Serial.print(" took on average ");
unsigned long average = getAverage(i);
Serial.print(average);
Serial.println(" uS");
}
Serial.println();
}
}
}
#endif