Optimización del código C para sistemas empotrados
La optimización del código C es necesaria para los sistemas empotrados porque, para ahorrar en costes de hardware, se planifica lo mínimo en términos de interfaz, memoria y capacidad de cálculo. En cambio, en un ordenador fijo se prefieren tiempos de ejecución bajos para no sobrecargar la CPU. La optimización tendrá dos objetivos contrapuestos
- reducir la cantidad de memoria utilizada por un programa
- aumentar la velocidad de ejecución de las funciones del programa
Un programa puede ser lo más rápido posible o lo más pequeño posible, pero no ambas cosas. Optimizar un criterio puede repercutir mucho en el otro.
A menudo, el compilador se encarga de optimizar el código, pero es esencial optimizarlo manualmente. Por lo general, nos concentramos en optimizar las secciones críticas del código y dejamos que el compilador se encargue del resto.
Aunque hay buenas prácticas a tener en cuenta a la hora de desarrollar código, éste sólo debe optimizarse cuando sea estrictamente necesario (límite de memoria, ejecución lenta). Por encima de todo, el código debe ser legible, mantenible y funcional.
Mejorar la eficacia del código
Funciones en línea
La palabra clave inline se utiliza para indicar al compilador que sustituya una llamada a la función por el código de la función. Cuando una función está presente en pocas secciones de código pero se llama a ella un gran número de veces, transformarla en una función inline puede mejorar el rendimiento de ejecución del código.
Tablas de consulta
Las búsquedas pueden utilizarse para sustituir funciones que requieren cálculos complicados por una simple asociación de variables. Por ejemplo, puede sustituir una función sin() o secciones swtich por tablas de opción múltiple.
Código ensamblador manual
Un compilador transforma el código C en código ensamblador optimizado. Cuando la función es crítica, un desarrollador experimentado puede crear su propio código ensamblador.
Reducción del tamaño del código (ROM)
Tamaño y tipo variables
Elegir correctamente la estructura, el tipo y el tamaño de la variable necesaria para almacenar los datos mejora considerablemente el rendimiento del código.
Declaración Goto
La función goto evita los algoritmos de árbol complicados. Esto dificulta la lectura del código y puede ser fuente de más errores.
Evite las bibliotecas estándar
Por lo general, las bibliotecas estándar son muy grandes y exigen muchos cálculos, porque intentan cubrir todos los casos. Desarrollar tus propias funciones para satisfacer tus necesidades específicas es una buena forma de optimizar tu código C#.
Reducir el uso de memoria (RAM)
Palabras clave const y static
Una variable estática es una variable a la que sólo se puede acceder en el contexto de una función, pero cuyo estado se mantiene entre llamadas a la función. Esto limita el uso de variables globales.
Probar el rendimiento del código
Medición del tiempo de ejecución de una sección de código mediante la biblioteca time.h
#include <iostream>
#include <time.h>
clock_t start, end;
double cpu_time_used;
int main()
{
std::cout << "Hello World" << std::endl;
start = clock();
for(;i<0xffffff;i++);
end = clock();
cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;
printf("Execution time: %f seconds\n", cpu_time_used);
return 0;
}
Uso de gprof
La herramienta gprof suministrada con el compilador proporciona tiempos de ejecución para diferentes secciones de código o funciones.
compilar con el indicador -pg para que el código genere un archivo gmon.out
g++ -g -Wall -no-pie -pg helloworld.cpp -o helloworld --include=include/utils.cpp #compile for profiling ./helloworld #execute and write gnom.out gprof -b helloworld.exe gmon.out > perfo.txt #translate gnom.out
Ejemplo de salida del fichero perfo.txt: se obtiene una tabla que contiene el tiempo de ejecución acumulado sobre el tiempo de ejecución total, el número de llamadas y el tiempo de ejecución medio.
Flat profile: Each sample counts as 0.01 seconds. % cumulative self self total time seconds seconds calls s/call s/call name 37.21 4.26 4.26 2 2.13 3.95 func1() 31.79 7.90 3.64 1 3.64 3.64 new_func1() 30.83 11.43 3.53 func2() 0.17 11.45 0.02 main 0.00 11.45 0.00 1 0.00 0.00 count_to(int, int)
Uso de la memoria
La opción de compilación -stats proporciona estadísticas generales sobre el código
g++ -g -Wall -no-pie -pg helloworld.cpp -o helloworld --include=include/utils.cpp --stats
resultado
(No per-node statistics) Type hash: size 32749, 23922 elements, 1.178881 collisions DECL_DEBUG_EXPR hash: size 1021, 0 elements, 0.000000 collisions DECL_VALUE_EXPR hash: size 1021, 18 elements, 0.031250 collisions decl_specializations: size 8191, 6113 elements, 1.427515 collisions type_specializations: size 8191, 3619 elements, 1.569889 collisions ****** time in header files (total): 0.862000 (38%) time in main file (total): 1.416000 (62%) ratio = 0.608757 : 1 ****** time in ./include/utils.cpp: 0.002000 (0%) time in <built-in>: 0.006000 (0%) time in <command-line>: 0.000000 (0%) time in <top level>: 0.009000 (0%)
Las opciones -fstack-usage y -Wstack-usage se utilizan para comprobar el uso de la memoria de pila en tiempo de compilación.
g++ -g -Wall -no-pie -pg helloworld.cpp -o helloworld --include=include/utils.cpp -fstack-usage
g++ -g -Wall -no-pie -pg helloworld.cpp -o helloworld --include=include/utils.cpp -Wstack-usage=32
El segundo comprueba el tamaño de memoria de pila esperado para un valor determinado (32).
In file included from <command-line>:
./include/utils.cpp: In function 'void count_to(int, int)':
./include/utils.cpp:6:6: warning: stack usage is 64 bytes [-Wstack-usage=]
6 | void count_to(int n, int delay)
| ^~~~~~~~
helloworld.cpp: In function 'void new_func1()':
helloworld.cpp:7:6: warning: stack usage is 64 bytes [-Wstack-usage=]
7 | void new_func1(void)
| ^~~~~~~~~
helloworld.cpp: In function 'void func1()':
helloworld.cpp:17:6: warning: stack usage is 64 bytes [-Wstack-usage=]
17 | void func1(void)
| ^~~~~
helloworld.cpp: In function 'void func2()':
helloworld.cpp:28:13: warning: stack usage is 64 bytes [-Wstack-usage=]
28 | static void func2(void)
| ^~~~~
helloworld.cpp: In function 'int main()':
helloworld.cpp:40:5: warning: stack usage is 96 bytes [-Wstack-usage=]
40 | int main(void)
Simulación de la huella de memoria en hardware específico
Es posible simular una determinada pieza de hardware con un script de enlace personalizado
myldscript.lds
| Memory | Start Address | Size |
| Internal Flash | 0x00000000 | 256 Kbytes |
| Internal SRAM | 0x20000000 | 32 Kbytes |
MEMORY
{
rom (rx) : ORIGIN = 0x00000000, LENGTH = 0x00040000
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00008000
}
STACK_SIZE = 0x2000;
/* Section Definitions */
SECTIONS
{
.text :
{
KEEP(*(.vectors .vectors.*))
*(.text*)
*(.rodata*)
} > rom
/* .bss section which is used for uninitialized data */
.bss (NOLOAD) :
{
*(.bss*)
*(COMMON)
} > ram
.data :
{
*(.data*);
} > ram AT >rom
/* stack section */
.stack (NOLOAD):
{
. = ALIGN(8);
. = . + STACK_SIZE;
. = ALIGN(8);
} > ram
_end = . ;
}
gcc -g -c helloworld.cpp # compile object file ld -o helloworld -T ldscript.lds helloworld.o --print-memory-usage #compile with specific link script
Memory region Used Size Region Size %age Used
rom: 12704 B 256 KB 4.85%
ram: 4128 B 32 KB 12.60%

