Basic Code Optimizations in C - GeeksforGeeks

28 Jun, 2019
Generally, resources are shared between different processes. Suppose your program takes more resources, then definitely it will affect the performance of other processes that need the same resources. So we have a need to write and optimize our program keeping in mind resources e.g. processor’s time and main memory.Following are some Optimization techniques
Optimizations in loopUnroll small loops: Most of the times Compiler does this automatically, but it is a good habit of writing optimized codes. Matrix updations using this is very advantageous.Program 1:#include <stdio.h>int main(void){    int fact[5];    fact[0] = 1;      // Overhead of managing a counter    // just for 4 iterations    // is not a good idea    for (int i = 1; i < 5; ++i) {        fact[i] = fact[i - 1] * i;    }    return 0;}Program 2:#include <stdio.h>int main(void){    int fact[5] = { 1, 1, 2, 6, 24 };      // Here the same work is done    // without counter overhead    return 0;}Avoid calculations in loop: We should avoid any calculation which is more or less constant in value. Inner loops should have minimum possible calculations.Program 1:#include <stdio.h>int main(void){    int arr[1000];    int a = 1, b = 5, c = 25, d = 7;      // Calculating a constant expression    // for each iteration is not good.    for (int i = 0; i < 1000; ++i) {        arr[i] = (((c % d) * a / b) % d) * i;    }    return 0;}Program 2:#include <stdio.h>  int main(void){    int arr[1000];    int a = 1, b = 5, c = 25, d = 7;      // pre calculating the constant expression    int temp = (((c % d) * a / b) % d);      for (int i = 0; i < 1000; ++i) {        arr[i] = temp * i;    }    return 0;}Avoid pointer Dereference in loop: Pointer dereferencing creates lots of trouble in memory. So better assign it to some temporary variable and then use that temporary variable in the loop.Program 1:#include <stdio.h>int main(void){    int a = 0;    int* iptr = &a;      // Dereferencing pointer inside loop    // is costly    for (int i = 1; i < 11; ++i) {        *iptr = *iptr + i;    }    printf("Value of a : %d", a);    return 0;}Output:Value of a : 55
Program 2:#include <stdio.h>int main(void){    int a = 0;    int* iptr = &a;      // Dereferencing pointer outside loop    // and saving its value in a temp variable    int temp = *iptr;      for (int i = 1; i < 11; ++i) {          // performing calculations on temp variable        temp = temp + i;    }      // Updating pointer using final value of temp    *iptr = temp;      printf("Value of a : %d", a);    return 0;}Output:Value of a : 55
Use Register variables as counters of inner loops: Variables stored in registers can be accessed much faster than variables stored in memory.Program:#include <stdio.h>int main(void){    register int i = 0;    register int j = 0;    int n = 5;      // using register variables    // as counters make the loop faster    for (i = 0; i < n; ++i) {        for (j = 0; j <= i; ++j) {            printf("* ");        }        printf("\n");    }    return 0;}Output:* 
* * 
* * * 
* * * * 
* * * * *
Fast MathematicsAvoid unnecessary Integer Division: Division operations are slow so we should minimize the division operations.Program:#include <stdio.h>int main(void){    int a = 100, b = 2, c = 5;    // int d=a/b/c;    two division operators    int d = a / (b * c); // single division operator    return 0;}Multiplication and division by power of 2: Use left shift(<<) for multiplication and right shift(>>) for division. The bit operations will be much faster than multiplication and division operations.For simple operations, the compiler may automatically optimize the code but in case of complex expressions it is always advised to use bit operations.Example :Multiply by 6 : a= a<<1 + a<<2; 
Multiply by 7 : a= a<<3 - a;
Divide by 8 : a= a>>3; // division by power of 2
Simplifying Expressions: Sometimes we can reduce some operations by simplifying expressions.Example : a*b + a*b*c + a*b*c*d ---> (a*b)*(1 + c*(1 + d)) 
 L.H.S can be Simplified to R.H.S
 L.H.S  : 6 multiplications and 2 additions
 R.H.S  : 3 multiplications and 2 additions
Optimization with Switch StatementCompilers translate switch statements in different ways. If case labels are small contiguous integer values, then it creates a jump table. This is very fast and doesn’t depend on the number of case labels also. If case labels are longer and not contiguous then it creates comparison tree i.e. if...else statements. So, in this case, we should keep the most frequent label first and the least frequent label should be at last.Sometimes we see a lot of repeated code written in all cases except one or two statementsExample :switch(expression)
{
case a:
       ........
       ........
       break;
case b:
       ........
       ........
       break;
case c:
       common statements;
       different statements;
       common statements;
       break;
case d:
       common statements;
       different statements;
       common statements;
       break;   '
case e:
       common statements;
       different statements;
       common statements;
       break;
case f:
       common statements;
       different statements;
       common statements;
       break;
default:
       break;      
}
We can take all cases together and can write common statements only once and different statements in related cases using another switch. Here we will take cases c, d, e, f together and write common statements, then we can use another switch and write different statements in case c, d, e, f. Then after this switch, we can again write common statements.switch(expression)
{
case a:
       ........
       ........
       break;
case b:
       ........
       ........
       break;
case c: 
case d: 
case e:   
case f:
       common statements;
       switch(expression);
       {
       case c:
              different statements;
              break;
       case d:
              different statements;
              break;
       case e:
              different statements;
              break;
       case f:
              different statements;
              break;
       }  /*End of switch*/
       common statements;
       break;     
       
default:
       break;      
}/*End of switch*/
Prefer int to char or shortWe should always prefer int to char because C performs all operations of char with an integer. In all operations like passing char to a function or an arithmetic operation, first char will be converted into integer and after compilation of operation, it will again be converted into char. For a single char, this may not affect the efficiency but suppose the same operation is performed 100 times in a loop then it can decrease the efficiency of the program.Prefer pre Increment/Decrement to post Increment/DecrementIn pre-increment, it first increments the value and just copies the value to variable location but in post-increment, it first copies the value to a temporary variable, increments it and then copies the value to the variable location. If post-increment is 1000 times in a loop it will decrement the efficiency.Order of Expression Evaluation A || B  
Here first A will be evaluated, if it’s true then there is no need of evaluation of expression B. So We should prefer to have an expression which evaluates to true most of the times, at the A’s place. A && B  
Here first A will be evaluated, if it’s false then there is no need of evaluation of expression B. So We should prefer to have an expression which evaluates to false most of the times, at the A’s place.
Optimizations in loopUnroll small loops: Most of the times Compiler does this automatically, but it is a good habit of writing optimized codes. Matrix updations using this is very advantageous.Program 1:#include <stdio.h>int main(void){    int fact[5];    fact[0] = 1;      // Overhead of managing a counter    // just for 4 iterations    // is not a good idea    for (int i = 1; i < 5; ++i) {        fact[i] = fact[i - 1] * i;    }    return 0;}Program 2:#include <stdio.h>int main(void){    int fact[5] = { 1, 1, 2, 6, 24 };      // Here the same work is done    // without counter overhead    return 0;}Avoid calculations in loop: We should avoid any calculation which is more or less constant in value. Inner loops should have minimum possible calculations.Program 1:#include <stdio.h>int main(void){    int arr[1000];    int a = 1, b = 5, c = 25, d = 7;      // Calculating a constant expression    // for each iteration is not good.    for (int i = 0; i < 1000; ++i) {        arr[i] = (((c % d) * a / b) % d) * i;    }    return 0;}Program 2:#include <stdio.h>  int main(void){    int arr[1000];    int a = 1, b = 5, c = 25, d = 7;      // pre calculating the constant expression    int temp = (((c % d) * a / b) % d);      for (int i = 0; i < 1000; ++i) {        arr[i] = temp * i;    }    return 0;}Avoid pointer Dereference in loop: Pointer dereferencing creates lots of trouble in memory. So better assign it to some temporary variable and then use that temporary variable in the loop.Program 1:#include <stdio.h>int main(void){    int a = 0;    int* iptr = &a;      // Dereferencing pointer inside loop    // is costly    for (int i = 1; i < 11; ++i) {        *iptr = *iptr + i;    }    printf("Value of a : %d", a);    return 0;}Output:Value of a : 55
Program 2:#include <stdio.h>int main(void){    int a = 0;    int* iptr = &a;      // Dereferencing pointer outside loop    // and saving its value in a temp variable    int temp = *iptr;      for (int i = 1; i < 11; ++i) {          // performing calculations on temp variable        temp = temp + i;    }      // Updating pointer using final value of temp    *iptr = temp;      printf("Value of a : %d", a);    return 0;}Output:Value of a : 55
Use Register variables as counters of inner loops: Variables stored in registers can be accessed much faster than variables stored in memory.Program:#include <stdio.h>int main(void){    register int i = 0;    register int j = 0;    int n = 5;      // using register variables    // as counters make the loop faster    for (i = 0; i < n; ++i) {        for (j = 0; j <= i; ++j) {            printf("* ");        }        printf("\n");    }    return 0;}Output:* 
* * 
* * * 
* * * * 
* * * * *

Unroll small loops: Most of the times Compiler does this automatically, but it is a good habit of writing optimized codes. Matrix updations using this is very advantageous.Program 1:#include <stdio.h>int main(void){    int fact[5];    fact[0] = 1;      // Overhead of managing a counter    // just for 4 iterations    // is not a good idea    for (int i = 1; i < 5; ++i) {        fact[i] = fact[i - 1] * i;    }    return 0;}Program 2:#include <stdio.h>int main(void){    int fact[5] = { 1, 1, 2, 6, 24 };      // Here the same work is done    // without counter overhead    return 0;}
Program 1:
#include <stdio.h>int main(void){    int fact[5];    fact[0] = 1;      // Overhead of managing a counter    // just for 4 iterations    // is not a good idea    for (int i = 1; i < 5; ++i) {        fact[i] = fact[i - 1] * i;    }    return 0;}
Program 2:
#include <stdio.h>int main(void){    int fact[5] = { 1, 1, 2, 6, 24 };      // Here the same work is done    // without counter overhead    return 0;}
Avoid calculations in loop: We should avoid any calculation which is more or less constant in value. Inner loops should have minimum possible calculations.Program 1:#include <stdio.h>int main(void){    int arr[1000];    int a = 1, b = 5, c = 25, d = 7;      // Calculating a constant expression    // for each iteration is not good.    for (int i = 0; i < 1000; ++i) {        arr[i] = (((c % d) * a / b) % d) * i;    }    return 0;}Program 2:#include <stdio.h>  int main(void){    int arr[1000];    int a = 1, b = 5, c = 25, d = 7;      // pre calculating the constant expression    int temp = (((c % d) * a / b) % d);      for (int i = 0; i < 1000; ++i) {        arr[i] = temp * i;    }    return 0;}
#include <stdio.h>int main(void){    int arr[1000];    int a = 1, b = 5, c = 25, d = 7;      // Calculating a constant expression    // for each iteration is not good.    for (int i = 0; i < 1000; ++i) {        arr[i] = (((c % d) * a / b) % d) * i;    }    return 0;}
Program 2:
#include <stdio.h>  int main(void){    int arr[1000];    int a = 1, b = 5, c = 25, d = 7;      // pre calculating the constant expression    int temp = (((c % d) * a / b) % d);      for (int i = 0; i < 1000; ++i) {        arr[i] = temp * i;    }    return 0;}
Avoid pointer Dereference in loop: Pointer dereferencing creates lots of trouble in memory. So better assign it to some temporary variable and then use that temporary variable in the loop.Program 1:#include <stdio.h>int main(void){    int a = 0;    int* iptr = &a;      // Dereferencing pointer inside loop    // is costly    for (int i = 1; i < 11; ++i) {        *iptr = *iptr + i;    }    printf("Value of a : %d", a);    return 0;}Output:Value of a : 55
Program 2:#include <stdio.h>int main(void){    int a = 0;    int* iptr = &a;      // Dereferencing pointer outside loop    // and saving its value in a temp variable    int temp = *iptr;      for (int i = 1; i < 11; ++i) {          // performing calculations on temp variable        temp = temp + i;    }      // Updating pointer using final value of temp    *iptr = temp;      printf("Value of a : %d", a);    return 0;}Output:Value of a : 55

#include <stdio.h>int main(void){    int a = 0;    int* iptr = &a;      // Dereferencing pointer inside loop    // is costly    for (int i = 1; i < 11; ++i) {        *iptr = *iptr + i;    }    printf("Value of a : %d", a);    return 0;}
Value of a : 55

Program 2:
#include <stdio.h>int main(void){    int a = 0;    int* iptr = &a;      // Dereferencing pointer outside loop    // and saving its value in a temp variable    int temp = *iptr;      for (int i = 1; i < 11; ++i) {          // performing calculations on temp variable        temp = temp + i;    }      // Updating pointer using final value of temp    *iptr = temp;      printf("Value of a : %d", a);    return 0;}
Value of a : 55

Use Register variables as counters of inner loops: Variables stored in registers can be accessed much faster than variables stored in memory.Program:#include <stdio.h>int main(void){    register int i = 0;    register int j = 0;    int n = 5;      // using register variables    // as counters make the loop faster    for (i = 0; i < n; ++i) {        for (j = 0; j <= i; ++j) {            printf("* ");        }        printf("\n");    }    return 0;}Output:* 
* * 
* * * 
* * * * 
* * * * *

#include <stdio.h>int main(void){    register int i = 0;    register int j = 0;    int n = 5;      // using register variables    // as counters make the loop faster    for (i = 0; i < n; ++i) {        for (j = 0; j <= i; ++j) {            printf("* ");        }        printf("\n");    }    return 0;}
* 
* * 
* * * 
* * * * 
* * * * *

Fast MathematicsAvoid unnecessary Integer Division: Division operations are slow so we should minimize the division operations.Program:#include <stdio.h>int main(void){    int a = 100, b = 2, c = 5;    // int d=a/b/c;    two division operators    int d = a / (b * c); // single division operator    return 0;}Multiplication and division by power of 2: Use left shift(<<) for multiplication and right shift(>>) for division. The bit operations will be much faster than multiplication and division operations.For simple operations, the compiler may automatically optimize the code but in case of complex expressions it is always advised to use bit operations.Example :Multiply by 6 : a= a<<1 + a<<2; 
Multiply by 7 : a= a<<3 - a;
Divide by 8 : a= a>>3; // division by power of 2
Simplifying Expressions: Sometimes we can reduce some operations by simplifying expressions.Example : a*b + a*b*c + a*b*c*d ---> (a*b)*(1 + c*(1 + d)) 
 L.H.S can be Simplified to R.H.S
 L.H.S  : 6 multiplications and 2 additions
 R.H.S  : 3 multiplications and 2 additions

Avoid unnecessary Integer Division: Division operations are slow so we should minimize the division operations.Program:#include <stdio.h>int main(void){    int a = 100, b = 2, c = 5;    // int d=a/b/c;    two division operators    int d = a / (b * c); // single division operator    return 0;}
#include <stdio.h>int main(void){    int a = 100, b = 2, c = 5;    // int d=a/b/c;    two division operators    int d = a / (b * c); // single division operator    return 0;}
Multiplication and division by power of 2: Use left shift(<<) for multiplication and right shift(>>) for division. The bit operations will be much faster than multiplication and division operations.For simple operations, the compiler may automatically optimize the code but in case of complex expressions it is always advised to use bit operations.Example :Multiply by 6 : a= a<<1 + a<<2; 
Multiply by 7 : a= a<<3 - a;
Divide by 8 : a= a>>3; // division by power of 2

Multiply by 6 : a= a<<1 + a<<2; 
Multiply by 7 : a= a<<3 - a;
Divide by 8 : a= a>>3; // division by power of 2

Simplifying Expressions: Sometimes we can reduce some operations by simplifying expressions.Example : a*b + a*b*c + a*b*c*d ---> (a*b)*(1 + c*(1 + d)) 
 L.H.S can be Simplified to R.H.S
 L.H.S  : 6 multiplications and 2 additions
 R.H.S  : 3 multiplications and 2 additions

 a*b + a*b*c + a*b*c*d ---> (a*b)*(1 + c*(1 + d)) 
 L.H.S can be Simplified to R.H.S
 L.H.S  : 6 multiplications and 2 additions
 R.H.S  : 3 multiplications and 2 additions

Optimization with Switch StatementCompilers translate switch statements in different ways. If case labels are small contiguous integer values, then it creates a jump table. This is very fast and doesn’t depend on the number of case labels also. If case labels are longer and not contiguous then it creates comparison tree i.e. if...else statements. So, in this case, we should keep the most frequent label first and the least frequent label should be at last.Sometimes we see a lot of repeated code written in all cases except one or two statementsExample :switch(expression)
{
case a:
       ........
       ........
       break;
case b:
       ........
       ........
       break;
case c:
       common statements;
       different statements;
       common statements;
       break;
case d:
       common statements;
       different statements;
       common statements;
       break;   '
case e:
       common statements;
       different statements;
       common statements;
       break;
case f:
       common statements;
       different statements;
       common statements;
       break;
default:
       break;      
}
We can take all cases together and can write common statements only once and different statements in related cases using another switch. Here we will take cases c, d, e, f together and write common statements, then we can use another switch and write different statements in case c, d, e, f. Then after this switch, we can again write common statements.switch(expression)
{
case a:
       ........
       ........
       break;
case b:
       ........
       ........
       break;
case c: 
case d: 
case e:   
case f:
       common statements;
       switch(expression);
       {
       case c:
              different statements;
              break;
       case d:
              different statements;
              break;
       case e:
              different statements;
              break;
       case f:
              different statements;
              break;
       }  /*End of switch*/
       common statements;
       break;     
       
default:
       break;      
}/*End of switch*/

Sometimes we see a lot of repeated code written in all cases except one or two statements
Example :
switch(expression)
{
case a:
       ........
       ........
       break;
case b:
       ........
       ........
       break;
case c:
       common statements;
       different statements;
       common statements;
       break;
case d:
       common statements;
       different statements;
       common statements;
       break;   '
case e:
       common statements;
       different statements;
       common statements;
       break;
case f:
       common statements;
       different statements;
       common statements;
       break;
default:
       break;      
}

We can take all cases together and can write common statements only once and different statements in related cases using another switch. Here we will take cases c, d, e, f together and write common statements, then we can use another switch and write different statements in case c, d, e, f. Then after this switch, we can again write common statements.
switch(expression)
{
case a:
       ........
       ........
       break;
case b:
       ........
       ........
       break;
case c: 
case d: 
case e:   
case f:
       common statements;
       switch(expression);
       {
       case c:
              different statements;
              break;
       case d:
              different statements;
              break;
       case e:
              different statements;
              break;
       case f:
              different statements;
              break;
       }  /*End of switch*/
       common statements;
       break;     
       
default:
       break;      
}/*End of switch*/

Prefer int to char or shortWe should always prefer int to char because C performs all operations of char with an integer. In all operations like passing char to a function or an arithmetic operation, first char will be converted into integer and after compilation of operation, it will again be converted into char. For a single char, this may not affect the efficiency but suppose the same operation is performed 100 times in a loop then it can decrease the efficiency of the program.
Prefer pre Increment/Decrement to post Increment/DecrementIn pre-increment, it first increments the value and just copies the value to variable location but in post-increment, it first copies the value to a temporary variable, increments it and then copies the value to the variable location. If post-increment is 1000 times in a loop it will decrement the efficiency.
Order of Expression Evaluation A || B  
Here first A will be evaluated, if it’s true then there is no need of evaluation of expression B. So We should prefer to have an expression which evaluates to true most of the times, at the A’s place. A && B  
Here first A will be evaluated, if it’s false then there is no need of evaluation of expression B. So We should prefer to have an expression which evaluates to false most of the times, at the A’s place.
 A || B  
Here first A will be evaluated, if it’s true then there is no need of evaluation of expression B. So We should prefer to have an expression which evaluates to true most of the times, at the A’s place.
 A || B  

Here first A will be evaluated, if it’s true then there is no need of evaluation of expression B. So We should prefer to have an expression which evaluates to true most of the times, at the A’s place.
 A && B  
Here first A will be evaluated, if it’s false then there is no need of evaluation of expression B. So We should prefer to have an expression which evaluates to false most of the times, at the A’s place.
 A && B  

Here first A will be evaluated, if it’s false then there is no need of evaluation of expression B. So We should prefer to have an expression which evaluates to false most of the times, at the A’s place.
C Basics
C Language
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.
TCP Server-Client implementation in C
Exception Handling in C++
'this' pointer in C++
Multithreading in C
Arrow operator -> in C/C++ with Examples
Multiple Inheritance in C++
Smart Pointers in C++ and How to Use Them
Understanding "extern" keyword in C
Ways to copy a vector in C++
Basics of File Handling in C