|
Page 1 of 3 Optimizing C and C++ Code Embedded software often runs on processors with limited computation power, thus optimizing the code becomes a necessity. In this article we will explore the following optimization techniques for C and C++ code developed for Real-time and Embedded Systems. - Adjust structure sizes to power of two
- Place case labels in narrow range
- Place frequent case labels first
- Break big switch statements into nested switches
- Minimize local variables
- Declare local variables in the inner most scope
- Reduce the number of parameters
- Use references for parameter passing and return value for types bigger than 4 bytes
- Don't define a return value if not used
- Consider locality of reference for code and data
- Prefer int over char and short
- Define lightweight constructors
- Prefer initialization over assignment
- Use constructor initialization lists
- Do not declare "just in case" virtual functions
- In-line 1 to 3 line functions
Adjust structure sizes to power of two When arrays of structures are involved, the compiler performs a multiply by the structure size to perform the array indexing. If the structure size is a power of 2, an expensive multiply operation will be replaced by an inexpensive shift operation. Thus keeping structure sizes aligned to a power of 2 will improve performance in array indexing. Place case labels in narrow range If the case labels are in a narrow range, the compiler does not generate a if-else-if cascade for the switch statement. Instead, it generates a jump table of case labels along with manipulating the value of the switch to index the table. This code generated is faster than if-else-if cascade code that is generated in cases where the case labels are far apart. Also, performance of a jump table based switch statement is independent of the number of case entries in switch statement. Place frequent case labels first If the case labels are placed far apart, the compiler will generate if-else-if cascaded code with comparing for each case label and jumping to the action for leg on hitting a label match. By placing the frequent case labels first, you can reduce the number of comparisons that will be performed for frequently occurring scenarios. Typically this means that cases corresponding to the success of an operation should be placed before cases of failure handling. Break big switch statements into nested switches The previous technique does not work for some compilers as they do not generate the cascade of if-else-if in the order specified in the switch statement. In such cases nested switch statements can be used to get the same effect. To reduce the number of comparisons being performed, judiciously break big switch statements into nested switches. Put frequently occurring case labels into one switch and keep the rest of case labels into another switch which is the default leg of the first switch. | Splitting a Switch Statement | // This switch statement performs a switch on frequent messages and handles the // infrequent messages with another switch statement in the default leg of the outer // switch statement
pMsg = ReceiveMessage(); switch (pMsg->type) { case FREQUENT_MSG1: handleFrequentMsg1(); break;
case FREQUENT_MSG2: handleFrequentMsg2(); break;
. . .
case FREQUENT_MSGn: handleFrequentMsgn(); break;
default: // Nested switch statement for handling infrequent messages. switch (pMsg->type) { case INFREQUENT_MSG1: handleInfrequentMsg1(); break;
case INFREQUENT_MSG2: handleInfrequentMsg2(); break;
. . .
case INFREQUENT_MSGm: handleInfrequentMsgm(); break; } }
|
|