¿Cuáles son algunas técnicas útiles para la optimización de memoria de software integrada?

¡Gracias por preguntar a Nabil! Me encantaría responder a esa pregunta, es una de mis especialidades. Acabo de terminar un proyecto con 3 procesadores en un sistema integrado de procesador distribuido, cada uno con solo 8K de código flash y 2K de RAM. Usando cada uno de los “trucos” a continuación, logré exprimir la funcionalidad requerida, pero fue un apretón. Dos de los tres procesadores cumplieron con los límites de su código. Aún así, me permitió crear un complejo sistema integrado con 3 microcontroladores de 50 centavos (1/2 dólar estadounidense). Por supuesto, para lo que guardé en el espacio, renuncié a algo de rendimiento, pero el rendimiento resultante fue lo suficientemente bueno para el sistema. El costo general del sistema fue el problema principal en este caso.

  • Si tiene algún resultado de texto, coloque todas las cadenas en una “tabla de cadenas” y acceda a ellas por medio de un puntero o índice de matriz. De esta manera, en lugar de tener múltiplos de las mismas cadenas (o incluso palabras de uso frecuente), coloque directamente en su printfs o sprintfs, puede usar los punteros para ellos. Esto me ahorra mucho espacio. El uso de tablas de cadenas también proporciona otro beneficio, si es necesario, facilita la creación de una versión en otro idioma “humano”, simplemente reemplaza las frases en inglés de la tabla con el otro idioma, y ​​tiene que hacer pocos o ningún cambio en su operativa. El código, en lugar de ir a lo largo de su código, tiene que cambiar el texto en todas partes que se imprime.
  • Si su compilador le permite hacerlo, elija las versiones “pequeña” o “pequeña” de cualquier biblioteca (I / O, matemáticas, etc.), a menos que, por supuesto, necesite las características que eliminaría. En la mayor parte de mi código no necesito más que las matemáticas de enteros simples, así que elimino el uso de la biblioteca matemática de punto flotante por completo. En la mayoría de los IDE, estos están en la configuración del compilador / enlazador.
  • No coloque el código en macros o funciones en línea, esto duplicará el código y causará la hinchazón. Si puede permitirse la ligera disminución del rendimiento de usar todas las funciones regulares, ahorrará espacio.
  • Por lo general, la RAM no es un problema para mí, pero si está utilizando demasiada RAM, intente eliminar la mayor cantidad posible de globales y, en su lugar, utilice las variables y los locales pasados. Las variables pasadas y los locales van a la pila y el espacio se reclama automáticamente cuando sale de la función. Por supuesto, es posible que necesite un tamaño de pila más grande para esto, pero en general, por lo general, vale la pena el compromiso
  • Si no necesita la funcionalidad completa de cualquiera de las funciones de la biblioteca estándar de C, pero tal vez solo un pequeño subconjunto de uno (estos están escritos para ser genéricos), cree su propia función solo para sus necesidades. Hago esto con printf, sprintf y vsprintf, ya que no necesito admitir todos los modificadores que debe tener una versión compatible, por lo que mis versiones “personalizadas” de estos son más pequeñas que las originales.
  • Por supuesto, puede optimizar en el compilador el tamaño en lugar de la velocidad, si puede permitirse la compensación. Solo recuerde que casi todos los compiladores incrustados con los que me he encontrado hacen algunas cosas divertidas para su capacidad de depurar el código cuando las optimizaciones están activadas. Solo haga esto durante la fase de depuración si está totalmente contra la pared y no le quedan otras opciones para ahorrar espacio de código.
  • (Para RAM) Compruebe el tamaño de su montón. Si no usa la asignación de memoria dinámica (malloc, alloc, libre, etc.), entonces no necesita un montón. Entonces puedes hacerlo muy pequeño o ponerlo en 0.
  • (Para RAM) Cree un código de prueba muy pequeño que precargue todo su espacio de pila con un patrón conocido (me gusta usar 0xAA), luego, después de ejecutar su código a través de todos sus ritmos por un tiempo, tratando de ejercitar todos los caminos posibles de flujo lógico – luego detenga su sistema y vea cuántos 0xAA se sobrescribieron. Esto le da una buena idea de la cantidad de pila que realmente necesita. Agregue un 10% a eso y luego, si puede reducir el tamaño de su pila en consecuencia. No te olvides de sacar el código de prueba cuando haya terminado.
  • Y, por supuesto, revise su código con mucho cuidado a la vista varias veces y vea si encuentra algún lugar donde pueda eliminar algún código o combinar alguna funcionalidad. Muchas veces tengo varias funciones que tienen un código similar en ellas, así que tomo ese código similar y lo convierto en su propia función “auxiliar” a la que llamo desde las funciones originales. Esto puede ser un gran ahorro de espacio, si puede permitirse una ligera reducción de rendimiento adicional debido a la sobrecarga de llamadas a la función. Mi regla general es que si se trata de 4, 5 o más líneas de código (dependiendo de su complejidad), muévalos a una pequeña “función auxiliar” si ese código puede reutilizarse en al menos otra función. Recuerde que mi sugerencia es solo una “regla general”, puede variar según el caso específico.

Si pienso en más adelante, podría regresar y editar esta respuesta para agregarlos, así que revíselos de vez en cuando por si lo hago.

¡¡¡¡Espero que esto ayude!!!! ¡Feliz codificación! 🙂

Además de lo que ya se ha dicho, considere lo siguiente:

  • Al agregar miembros a la estructura, considere agregar los miembros más grandes antes que los miembros pequeños (uint32 antes de uint8) para minimizar el desperdicio de relleno.
  • Si puede pagar el costo de rendimiento, considere desactivar el empaquetado de miembros en las estructuras.
  • Si usa una arquitectura que admite una versión comprimida de la instrucción (como el conjunto de instrucciones ARM Thumb), considere la posibilidad de habilitarla.
  • Si tiene varios miembros en una estructura que solo usa uno a la vez, es hora de cambiar a sindicatos.
  • Utilice los campos de bits para representar datos que realmente necesitan menos de 8 bits.
  • Trate de usar máscaras de bits en lugar de múltiples banderas.

Tenga en cuenta que al comprimir la memoria se reducirá el rendimiento, lo que significa un tiempo de ejecución más prolongado, lo que significa más ciclos de CPU, lo que significa un mayor consumo de energía.

Solicite a gcc que optimice el tamaño en lugar de la velocidad. Cuando esté listo para generar código de producción, use Gnu strip para eliminar los símbolos innecesarios.