He trabajado en el desarrollo de una función en un producto particular nuestro. Ha habido una solicitud para portar la misma característica a otro producto. Este producto se basa en un microcontrolador M16C, que tradicionalmente tiene 64K Flash y 2k de RAM.
Es un producto maduro y, por lo tanto, solo le quedan 132 Bytes de Flash y 2 Bytes de RAM.
Para portar la característica solicitada (la característica misma ha sido optimizada), necesito 1400 bytes de Flash y ~ 200 Bytes de RAM.
¿Alguien tiene alguna sugerencia sobre cómo recuperar estos bytes por compactación de código? ¿Qué cosas específicas busco cuando intento compactar el código de trabajo ya existente?
Cualquier idea será realmente apreciada.
Gracias.
Respuestas:
Tiene un par de opciones: la primera es buscar código redundante y moverlo a una sola llamada para deshacerse de la duplicación; El segundo es eliminar la funcionalidad.
Eche un buen vistazo a su archivo .map y vea si hay funciones de las que pueda deshacerse o reescribir. También asegúrese de que las llamadas a la biblioteca que se están utilizando sean realmente necesarias.
Ciertas cosas como la división y las multiplicaciones pueden traer mucho código, pero el uso de cambios y un mejor uso de las constantes puede hacer que el código sea más pequeño. También eche un vistazo a cosas como las constantes de cadena y
printf
s. Por ejemplo cadaprintf
consumirá su rom, pero es posible que pueda tener un par de cadenas de formato compartidas en lugar de repetir esa cadena constantemente una y otra vez.En cuanto a la memoria, vea si puede deshacerse de los globales y usar autos en una función. También evite tantas variables en la función principal como sea posible, ya que estas consumen memoria al igual que lo hacen los globales.
fuente
Siempre vale la pena mirar la salida del archivo de lista (ensamblador) para buscar cosas en las que su compilador particular es particularmente malo.
Por ejemplo, puede encontrar que las variables locales son muy caras, y si la aplicación es lo suficientemente simple como para que valga la pena el riesgo, mover algunos contadores de bucle a variables estáticas puede ahorrar mucho código.
O la indexación de matrices puede ser muy costosa pero las operaciones de puntero mucho más baratas. O viceversa.
Pero mirar el lenguaje ensamblador es el primer paso.
fuente
Las optimizaciones del compilador, por ejemplo,
-Os
en GCC ofrecen el mejor equilibrio entre la velocidad y el tamaño del código. Evite-O3
, ya que puede aumentar el tamaño del código.fuente
Para RAM, verifique el rango de todas sus variables: ¿está usando ints donde podría usar un char? ¿Son los amortiguadores más grandes de lo que necesitan ser?
La compresión de código depende mucho de la aplicación y del estilo de codificación. Sus cantidades restantes sugieren que tal vez el código ya se haya eliminado, aunque puede significar que queda poco.
También observe detenidamente la funcionalidad general: ¿hay algo que no se usa realmente y que se puede descartar?
fuente
Si es un proyecto antiguo pero el compilador se ha desarrollado desde entonces, podría ser que un compilador más reciente produzca un código más pequeño
fuente
Siempre vale la pena consultar el manual del compilador para ver las opciones para optimizar el espacio.
Para gcc
-ffunction-sections
y-fdata-sections
con el--gc-sections
indicador de enlace son buenos para quitar el código muerto.Aquí hay algunos otros excelentes consejos (orientados a AVR)
fuente
Puede examinar la cantidad de espacio de pila y espacio de almacenamiento dinámico que se asignan. Es posible que pueda recuperar una cantidad sustancial de RAM si alguno de los dos está sobreasignado.
Supongo que para un proyecto que cabe en 2k de RAM para comenzar, no hay asignación de memoria dinámica (uso de
malloc
,calloc
, etc.). Si este es el caso, puede deshacerse de su montón por completo suponiendo que el autor original haya dejado algo de RAM asignada para el montón.Debe tener mucho cuidado al reducir el tamaño de la pila, ya que esto puede causar errores que son muy difíciles de encontrar. Puede ser útil comenzar inicializando todo el espacio de la pila a un valor conocido (algo diferente a 0x00 o 0xff ya que estos valores ya ocurren comúnmente) y luego ejecutar el sistema durante un tiempo para ver cuánto espacio de la pila no se usa.
fuente
¿Su código usa matemática de punto flotante? Es posible que pueda volver a implementar sus algoritmos usando matemática entera solamente, y eliminar los gastos generales de usar la biblioteca de coma flotante C. Por ejemplo, en algunas aplicaciones, funciones como seno, registro, exp pueden reemplazarse por aproximaciones polinómicas enteras.
¿Utiliza su código tablas de búsqueda grandes para algún algoritmo, como los cálculos de CRC? Puede intentar sustituir una versión diferente del algoritmo que calcula los valores sobre la marcha, en lugar de utilizar las tablas de búsqueda. La advertencia es que el algoritmo más pequeño es más lento, así que asegúrese de tener suficientes ciclos de CPU.
¿Su código tiene grandes cantidades de datos constantes, como tablas de cadenas, páginas HTML o gráficos de píxeles (iconos)? Si es lo suficientemente grande (digamos 10 kB), podría valer la pena implementar un esquema de compresión muy simple para reducir los datos y descomprimirlos sobre la marcha cuando sea necesario.
fuente
Puede intentar reorganizar para codificar mucho, a un estilo más compacto. Depende mucho de lo que esté haciendo el código. La clave es encontrar cosas similares y volver a implementarlas en términos mutuos. Un extremo sería utilizar un lenguaje de nivel superior, como Forth, con el que puede ser más fácil lograr una densidad de código más alta que en C o ensamblador.
Aquí está Forth para M16C .
fuente
Establecer el nivel de optimización del compilador. Muchos IDE tienen configuraciones que permiten optimizaciones de tamaño de código a expensas del tiempo de compilación (o tal vez incluso el tiempo de procesamiento en algunos casos). Pueden lograr la compactación de código volviendo a ejecutar su optimizador un par de veces, buscando patrones optimizables menos comunes y una gran cantidad de trucos que pueden no ser necesarios para la compilación casual / depuración. Por lo general, de manera predeterminada, los compiladores están configurados en un nivel medio de optimización. Excava en la configuración y deberías poder encontrar alguna escala de optimización basada en enteros.
fuente
Si ya está utilizando un compilador de nivel profesional como IAR, creo que tendrá dificultades para obtener ahorros importantes mediante pequeños ajustes de código de bajo nivel; deberá buscar más para eliminar la funcionalidad o hacer grandes reescribe partes de una manera más eficiente. Tendrá que ser un codificador más inteligente que el que escribió la versión original ... En cuanto a la RAM, debe analizar detenidamente cómo se usa actualmente y ver si hay margen para superponer el uso de la misma RAM para diferentes cosas en diferentes momentos (los sindicatos son útiles para esto). Los tamaños de pila y pila predeterminados de IAR en los ARM / AVR que he tendido a ser demasiado generosos, por lo que estos serían lo primero que se debería considerar.
fuente
Algo más para verificar (algunos compiladores en algunas arquitecturas copian constantes en la RAM), que generalmente se usa cuando el acceso a las constantes flash es lento / difícil (por ejemplo, AVR), por ejemplo, el compilador AVR de IAR requiere un calificador _ _flash para no copiar una constante en la RAM)
fuente
Si su procesador no tiene soporte de hardware para un parámetro / pila local, pero el compilador intenta implementar una pila de parámetros en tiempo de ejecución de todos modos, y si su código no necesita ser reentrante, puede guardar el código espacio asignando estáticamente variables automáticas. En algunos casos, esto debe hacerse manualmente; en otros casos, las directivas del compilador pueden hacerlo. La asignación manual eficiente requerirá el intercambio de variables entre rutinas. Este intercambio debe realizarse con cuidado, para garantizar que ninguna rutina use una variable que otra rutina considere "en alcance", pero en algunos casos los beneficios del tamaño del código pueden ser significativos.
Algunos procesadores tienen convenciones de llamada que pueden hacer que algunos estilos de paso de parámetros sean más eficientes que otros. Por ejemplo, en los controladores PIC18, si una rutina toma un solo parámetro de un byte, se puede pasar en un registro; si se necesita más que eso, todos parámetros deben pasarse en la RAM. Si una rutina tomara dos parámetros de un byte, puede ser más eficiente "pasar" uno en una variable global y luego pasar el otro como parámetro. Con rutinas ampliamente utilizadas, los ahorros pueden sumar. Pueden ser especialmente significativos si el parámetro pasado a través de global es un indicador de un solo bit, o si generalmente tendrá un valor de 0 o 255 (ya que existen instrucciones especiales para almacenar un 0 o 255 en la RAM).
En el ARM, poner variables globales que se usan frecuentemente juntas en una estructura puede reducir significativamente el tamaño del código y mejorar el rendimiento. Si A, B, C, D y E son variables globales separadas, entonces el código que usa todas ellas debe cargar la dirección de cada una en un registro; Si no hay suficientes registros, puede ser necesario volver a cargar esas direcciones varias veces. Por el contrario, si forman parte de la misma estructura global de MyStuff, entonces el código que usa MyStuff.A, MyStuff.B, etc. simplemente puede cargar la dirección de MyStuff una vez. Gran victoria.
fuente
1.Si su código se basa en muchas estructuras, asegúrese de que los miembros de la estructura estén ordenados de los que ocupan menos memoria.
Ej: "uint32_t uint16_t uint8_t" en lugar de "uint16_t uint8_t uint32_t"
Esto asegurará una estructura mínima de relleno.
2. Use const para variables donde corresponda. Esto asegurará que esas variables estarán en ROM y no comerán RAM
fuente
Algunos trucos (quizás obvios) que he usado con éxito para comprimir el código de algunos clientes:
Banderas compactas en campos de bits o máscaras de bits. Esto puede ser beneficioso ya que generalmente los booleanos se almacenan como enteros, lo que desperdicia memoria. Esto ahorrará RAM y ROM y, por lo general, el compilador no lo hace.
Busque redundancia en el código y use bucles o funciones para ejecutar declaraciones repetidas.
También he guardado algo de ROM reemplazando muchas
if(x==enum_entry) <assignment>
declaraciones de constantes con una matriz indexada, cuidando que las entradas de enumeración se puedan usar como índice de matrizfuente
Si puede, use funciones en línea o macros de compilación en lugar de funciones pequeñas. Hay sobrecarga de tamaño y velocidad con argumentos de paso y tales que pueden remediarse haciendo que la función esté en línea.
fuente
int get_a(struct x) {return x.a;}
Cambie las variables locales para que tengan el mismo tamaño de sus registros de CPU.
Si la CPU es de 32 bits, use variables de 32 bits incluso si el valor máximo nunca superará 255. Si utilizó una variable de 8 bits, el compilador agregará código para enmascarar los 24 bits superiores.
El primer lugar que miraría es en las variables de bucle for.
Esto puede parecer un buen lugar para una variable de 8 bits, pero una variable de 32 bits puede producir menos código.
fuente