Optimizando memoria Flash en Arduino

En una entrega anterior, presentamos un truco para optimizar el uso de la memoria RAM de su Arduino, en perjuicio de la memoria Flash. El truco consistia en alojar las constantes de texto en memoria Flash en lugar de memoria RAM. Sin embargo, existen otros casos en donde queremos optimizar el uso de la memoria Flash sin perjuicio del uso de la memoria RAM.

Compartiendo inquietudes similares a la anterior en foros y grupos, me encontré con recomendaciones simplificadas, como por ejemplo: “Usa un Arduino más grande!”. Pero a veces eso no es posible o no es deseable, y debemos ser inteligente en el uso de la memoria Flash si no queremos quedarnos sin espacio.

Uno de los peligros de aprender a programar mejor y mejor es que uno programa para que el código sea simple, o elegante, o rápido, pero rara vez para que ocupe menos espacio. De hecho, es común entrar en malos hábitos que nos costarán memoria Flash extra a cambio de un código más profesional.

Si usted está buscando trucos para optimizar su Sketch, asumo que ya sabe programar bastante bien. Los siguientes trucos asumen que ustede ya conoce los conceptos presentados.

¿If()…else o switch()?

Es bastante común tener un Sketch con muchos if(). También es bastante común que use varios if() encadenados para revisar varias opciones entre una, como por ejemplo el siguiente código:

void setup() {
  Serial.begin(9600);
}

void loop() {
  char ch;
  
  if (Serial.available()) {
    ch = Serial.read();

    if (ch == '1') {
      delay(100);
    }
    else if (ch == '2') {
      delay(200);
    }
    else if (ch == '3') {
      delay(300);
    }
    else if (ch == '4') {
      delay(400);
    }
    else {
      delay(1000);
    }
  }
}

Como se habrán dado cuenta, el código anterior puede escribirse de una manera más elegante. A continución les dejo la version con switch():

void setup() {
  Serial.begin(9600);
}

void loop() {
  char ch;
  
  if (Serial.available()) {
    ch = Serial.read();

    switch(ch) {
      case '1': delay(100); break;
      case '2': delay(200); break;
      case '3': delay(300); break;
      case '4': delay(400); break;
      default: delay(1000);
    }
  }
}

Mucho más elegante y simple de entender, no? Pero, ¿cuál cree que ocupa más memoria Flash? Nuestra apuesta lógica fue por la versión que  switch().

Luego de poner los 2 Sketches en Arduino IDE, y compilarlos quedamos sorprendidos:

Versión con if(): 1708 bytes
Versión con switch(): 1710 bytes

¡La versión con switch ocupa 2 bytes más! Es cierto que 2 bytes dificilmente cambie su trabajo pero cuando trabaja con Sketches muy grandes, la posibilidad de que tenga muchos switch() es alta, y 2 bytes aqui, 2 bytes allá, tal vez pueda juntar algo de memoria extra para lo que necesite terminar. Además, puede sumar esta optimización a otras para lograr una diferencia más grande.

¿Código duplicado o no?

Otro gran mito es que el código duplicado tiene todas desventajas. Pero es realmente así? Veamos un ejemplo:

void setup() {
  Serial.begin(9600);
}

void loop() {
  char ch;
  
  if (Serial.available()) {
    ch = Serial.read();

    switch(ch) {
      case '1': 
        Serial.print("Usted presiono: "); 
        Serial.println(ch);
        break;
      case '2':
        Serial.print("Usted presiono: "); 
        Serial.println(ch);
        break;
      case '3':
        Serial.print("Usted presiono: "); 
        Serial.println(ch);
        break;
      case '4':
        Serial.print("Usted presiono: "); 
        Serial.println(ch);
        break;
      default:
        Serial.print("Usted presiono: "); 
        Serial.println(ch);
        break;
    }
  }
}

El código anterior puede que no tenga mucho sentido para usted ahora, pero es común ver en un Sketch que la gente maneja errores de una manera similar a la anterior. Veamos como mejorar el código siguiendo las reglas de programación que trata de evitar código redundante o duplicado:

void setup() {
  Serial.begin(9600);
}

void loop() {
  char ch;
  
  if (Serial.available()) {
    ch = Serial.read();

    switch(ch) {
      case '1': 
        mostrar(ch);
        break;
      case '2':
        mostrar(ch);
        break;
      case '3':
        mostrar(ch);
        break;
      case '4':
        mostrar(ch);
        break;
      default:
        mostrar(ch);
        break;
    }
  }
}

void mostrar(char ch) {
  Serial.print("Usted presiono: "); 
  Serial.println(ch);  
}

Y luego de compilar y observar el espacio usado por acada uno de ellos:

Código duplicado: 1602 bytes
Código con función: 1566 bytes

Y las evidencias hablan por sí solas, el Sketch que usa la función y evita el código redundante es claramente mejor a la hora de ahorrar memoria Flash.

Conclusión

Creo que además de los trucos y ejemplos anteriores, el mejor consejo es que no dé nada por sentado. Muchas de las cosas que descubri lo hago probando, analizando y evaluando los resultados. El compilador es su mejor amigo y siempre existe una manera alternativa de hacer las cosas. Que las alternativas a su código sean mejores en algún aspecto, es algo que deberá probar, y no siempre la optimización va para el lado que queremos.

Suerte con sus Sketches!

Deja un comentario