Optimizaciones de código básico en C

Generalmente, los recursos se comparten entre diferentes procesos. Suponga que su programa toma más recursos, entonces definitivamente afectará el rendimiento de otros procesos que necesitan los mismos recursos. Entonces tenemos la necesidad de escribir y optimizar nuestro programa teniendo en cuenta los recursos, por ejemplo, el tiempo del procesador y la memoria principal.
Las siguientes son algunas técnicas de optimización.

  1. Optimizaciones en bucle
    • Desenrolle pequeños bucles : la mayoría de las veces Compiler hace esto automáticamente, pero es un buen hábito escribir códigos optimizados. Las actualizaciones de array que usan esto son muy ventajosas.

      Programa 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;
      }

      Programa 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;
      }
    • Evitar cálculos en bucle : debemos evitar cualquier cálculo que sea más o menos constante en valor. Los bucles internos deben tener el mínimo de cálculos posibles.
      Programa 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;
      }

      Programa 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;
      }
    • Evite la desreferenciación del puntero en bucle : la desreferenciación del puntero crea muchos problemas en la memoria. Así que mejor asígnalo a alguna variable temporal y luego usa esa variable temporal en el bucle.
      Programa 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;
      }
      Producción:

      Value of a : 55
      

      Programa 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;
      }
      Producción:

      Value of a : 55
      
    • Utilice variables de registro como contadores de bucles internos : se puede acceder a las variables almacenadas en registros mucho más rápido que a las variables almacenadas en la memoria.
      Programa:

      #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;
      }
      Producción:

      * 
      * * 
      * * * 
      * * * * 
      * * * * *
      
  2. Matemáticas Rápidas
    • Evite la división de enteros innecesaria : las operaciones de división son lentas, por lo que debemos minimizar las operaciones de división.
      Programa:

      #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;
      }
    • Multiplicación y división por potencia de 2 : use el desplazamiento a la izquierda (<<) para la multiplicación y el desplazamiento a la derecha (>>) para la división. Las operaciones de bits serán mucho más rápidas que las operaciones de multiplicación y división. Para operaciones simples, el compilador puede optimizar automáticamente el código, pero en el caso de expresiones complejas, siempre se recomienda usar operaciones de bits.
      Ejemplo :
      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
      
    • Simplificación de expresiones: A veces podemos reducir algunas operaciones simplificando expresiones.
      Ejemplo :
       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
      
  3. Optimización con los
    compiladores de sentencias Switch traducen las sentencias Switch de diferentes maneras. Si las etiquetas de casos son pequeños valores enteros contiguos, se crea una tabla de salto. Esto es muy rápido y no depende del número de etiquetas de cajas. Si las etiquetas de los casos son más largas y no contiguas, se crea un árbol de comparación, es decir, declaraciones if…else. Entonces, en este caso, debemos mantener la etiqueta más frecuente primero y la etiqueta menos frecuente debe estar al final.

    A veces vemos mucho código repetido escrito en todos los casos excepto en una o dos sentencias

    Ejemplo :

    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;      
    }
    

    Podemos tomar todos los casos juntos y podemos escribir declaraciones comunes solo una vez y declaraciones diferentes en casos relacionados usando otro interruptor. Aquí tomaremos los casos c, d, e, f juntos y escribiremos declaraciones comunes, luego podemos usar otro interruptor y escribir declaraciones diferentes en el caso c, d, e, f. Luego, después de este cambio, podemos volver a escribir declaraciones comunes.

    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*/
    
  4. Preferir int a char o short
    Siempre debemos preferir int a char porque C realiza todas las operaciones de char con un número entero. En todas las operaciones, como pasar un carácter a una función o una operación aritmética, el primer carácter se convertirá en un número entero y, después de la compilación de la operación, se convertirá nuevamente en carácter . Para un solo carácter, esto puede no afectar la eficiencia, pero supongamos que la misma operación se realiza 100 veces en un ciclo, entonces puede disminuir la eficiencia del programa.
  5. Prefiere incremento/decremento previo a incremento/decremento posterior
    En pre-incremento, primero incrementa el valor y simplemente copia el valor en la ubicación de la variable pero en post-incremento, primero copia el valor en una variable temporal, lo incrementa y luego copia el valor valor a la ubicación de la variable. Si el incremento posterior es 1000 veces en un ciclo, disminuirá la eficiencia.
  6. Orden de Evaluación de la Expresión
    •  A || B  
      

      Aquí primero se evaluará A, si es verdadera, entonces no hay necesidad de evaluar la expresión B. Por lo tanto, deberíamos preferir tener una expresión que se evalúe como verdadera la mayoría de las veces, en el lugar de A.

    •  A && B  
      

      Aquí primero se evaluará A, si es falso, entonces no hay necesidad de evaluar la expresión B. Por lo tanto, deberíamos preferir tener una expresión que se evalúe como falsa la mayoría de las veces, en el lugar de A.

Publicación traducida automáticamente

Artículo escrito por RishavPandey1 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *