Otimização do código C para sistemas incorporados
A otimização do código C é necessária para os sistemas incorporados porque, para poupar nos custos de hardware, planeamos o mínimo necessário em termos de interface, memória e capacidade de computação. No entanto, para um computador fixo, são preferíveis tempos de execução baixos, para evitar sobrecarregar a CPU. A otimização terá dois objectivos concorrentes
- reduzir a quantidade de memória utilizada por um programa
- aumentar a velocidade de execução das funções do programa
Um programa pode ser tão rápido quanto possível ou tão pequeno quanto possível, mas não ambos. A otimização de um critério pode ter um impacto importante no outro.
Muitas vezes, o compilador encarrega-se de otimizar o código, mas é essencial optimizá-lo manualmente. Geralmente, concentramo-nos em otimizar as secções críticas do código e deixamos o compilador tratar do resto.
Embora existam boas práticas a ter em conta no desenvolvimento do código, este só deve ser optimizado quando estritamente necessário (limite de memória, execução lenta). Acima de tudo, o código deve ser legível, fácil de manter e funcional.
Melhorar a eficiência do código
Funções em linha
A palavra-chave inline é utilizada para dizer ao compilador para substituir uma chamada à função pelo código da função. Quando uma função está presente em poucas secções de código, mas é chamada um grande número de vezes, transformar a função numa função em linha pode melhorar o desempenho da execução do código.
Tabelas de pesquisa
As pesquisas podem ser utilizadas para substituir funções que requerem cálculos complicados por uma simples associação de variáveis. Por exemplo, pode substituir uma função sin() ou secções swtich por tabelas de escolha múltipla.
Código assembler manual
Um compilador transforma o código C em código de montagem optimizado. Quando a função é crítica, um programador experiente pode criar o seu próprio código de montagem.
Reduzir a dimensão do código (ROM)
Tamanho e tipo variáveis
Escolher a estrutura, o tipo e o tamanho corretos da variável necessária para armazenar dados melhora consideravelmente o desempenho do código.
Ir para a declaração
A função goto evita algoritmos de árvore complicados. Isto torna o código mais difícil de ler e pode ser a fonte de mais erros.
Evitar bibliotecas padrão
As bibliotecas padrão são geralmente muito grandes e computacionalmente intensivas porque tentam cobrir todos os casos. Desenvolver as suas próprias funções para satisfazer as suas necessidades específicas é uma boa forma de otimizar o seu código C#.
Reduzir a utilização da memória (RAM)
Palavras-chave Const e estáticas
Uma variável estática é uma variável que só pode ser acedida no contexto de uma função, mas cujo estado é mantido entre chamadas de função. Isto limita a utilização de variáveis globais.
Testar o desempenho do código
Medir o tempo de execução de uma secção de código utilizando a 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;
}
Utilizar o gprof
A ferramenta gprof fornecida com o compilador fornece tempos de execução para diferentes secções de código ou funções.
compilar com o sinalizador -pg para que o código gere um ficheiro 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
Exemplo de saída do ficheiro perfo.txt: obtém-se uma tabela com o tempo de execução acumulado sobre o tempo total de execução, o número de chamadas e o tempo médio de execução.
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)
Utilização da memória
A opção de compilação -stats fornece estatísticas gerais sobre o 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%)
As opções -fstack-usage e -Wstack-usage são usadas para verificar o uso da memória da pilha em tempo de compilação.
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
O segundo verifica o tamanho da memória da pilha esperado para um determinado valor (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)
Simulação da pegada de memória em hardware específico
É possível simular uma determinada peça de hardware com um script de ligação 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%

