Eu trabalhei no desenvolvimento de um recurso em um produto específico. Houve um pedido para portar o mesmo recurso para outro produto. Este produto é baseado em um microcontrolador M16C, que tradicionalmente possui 64K Flash e 2k de RAM.
É um produto maduro e, portanto, possui apenas 132 bytes de Flash e 2 bytes de RAM restantes.
Para portar o recurso solicitado (o próprio recurso foi otimizado), preciso de 1400 bytes de Flash e ~ 200 bytes de RAM.
Alguém tem alguma sugestão sobre como recuperar esses bytes por compactação de código? Que coisas específicas procuro quando estou tentando compactar o código de trabalho já existente?
Todas as idéias serão realmente apreciadas.
Obrigado.
Respostas:
Você tem algumas opções: primeiro é procurar código redundante e movê-lo para uma única chamada para se livrar da duplicação; o segundo é remover a funcionalidade.
Dê uma boa olhada no seu arquivo .map e veja se há funções das quais você pode se livrar ou reescrever. Verifique também se as chamadas de biblioteca que estão sendo usadas são realmente necessárias.
Certas coisas, como divisão e multiplicações, podem trazer muito código, mas o uso de turnos e um melhor uso de constantes podem diminuir o código. Também dê uma olhada em coisas como constantes de string
printf
es. Por exemplo, cadaprintf
um consumirá sua rom, mas você poderá ter algumas seqüências de caracteres de formato compartilhado, em vez de repetir essa sequência constantemente uma e outra vez.Para a memória, veja se você pode se livrar de globais e usar automóveis em uma função. Evite também o maior número possível de variáveis na função principal, pois elas consomem memória da mesma forma que os globais.
fonte
Sempre vale a pena examinar a saída do arquivo de listagem (assembler) para procurar coisas nas quais seu compilador é particularmente ruim.
Por exemplo, você pode achar que as variáveis locais são muito caras e, se o aplicativo é simples o suficiente para valer o risco, mover alguns contadores de loop para variáveis estáticas pode economizar muito código.
Ou a indexação de matriz pode ser muito cara, mas as operações com ponteiros são muito mais baratas. Ou vice-versa.
Mas olhar para a linguagem assembly é o primeiro passo.
fonte
As otimizações do compilador, por exemplo,
-Os
no GCC oferecem o melhor equilíbrio entre velocidade e tamanho do código. Evite-O3
, pois isso pode aumentar o tamanho do código.fonte
Para RAM, verifique o intervalo de todas as suas variáveis - você está usando ints onde poderia usar um caractere? Os buffers são maiores do que precisam?
A compressão de código depende muito do aplicativo e do estilo de codificação. Seus valores restantes sugerem que talvez o código já tenha passado por alguns comprimentos, o que pode significar que resta pouco.
Também dê uma olhada na funcionalidade geral - existe algo que não é realmente usado e pode ser descartado?
fonte
Se é um projeto antigo, mas o compilador foi desenvolvido desde então, pode ser que um compilador mais recente produza um código menor
fonte
Sempre vale a pena conferir o manual do compilador em busca de opções para otimizar o espaço.
Para o gcc
-ffunction-sections
e-fdata-sections
com o--gc-sections
sinalizador do linker, é bom remover o código morto.Aqui estão algumas outras dicas excelentes (voltadas para o AVR)
fonte
Você pode examinar a quantidade de espaço de pilha e de pilha que estão alocados. Você poderá recuperar uma quantidade substancial de RAM se um ou ambos estiverem com excesso de alocação.
Meu palpite é que, para começar um projeto que se encaixa em 2k de RAM, não há alocação dinâmica de memória (o uso de
malloc
,calloc
, etc.). Se esse for o caso, você pode se livrar completamente do heap, assumindo que o autor original tenha deixado alguma RAM alocada para o heap.Você precisa ter muito cuidado ao reduzir o tamanho da pilha, pois isso pode causar bugs que são muito difíceis de encontrar. Pode ser útil começar inicializando todo o espaço da pilha para um valor conhecido (algo diferente de 0x00 ou 0xff, pois esses valores já ocorrem normalmente) e, em seguida, execute o sistema por um tempo para ver quanto espaço da pilha não está sendo usado.
fonte
Seu código usa matemática de ponto flutuante? Você poderá reimplementar seus algoritmos usando apenas matemática inteira e eliminar as despesas gerais do uso da biblioteca de ponto flutuante C. Por exemplo, em algumas aplicações, funções como sine, log, exp podem ser substituídas por aproximações polinomiais inteiras.
Seu código usa grandes tabelas de pesquisa para algoritmos, como cálculos de CRC? Você pode tentar substituir uma versão diferente do algoritmo que calcula valores rapidamente, em vez de usar as tabelas de consulta. A ressalva é que o algoritmo menor provavelmente é mais lento, portanto, verifique se você tem ciclos de CPU suficientes.
Seu código possui grandes quantidades de dados constantes, como tabelas de string, páginas HTML ou gráficos de pixel (ícones)? Se for grande o suficiente (digamos 10 kB), pode valer a pena implementar um esquema de compactação muito simples para reduzir os dados e descompactá-los quando necessário.
fonte
Você pode tentar reorganizar para codificar muito, para um estilo mais compacto. Depende muito do que o código está fazendo. A chave é encontrar coisas semelhantes e reimplementá-las em termos uma da outra. Um extremo seria usar uma linguagem de nível superior, como a Forth, com a qual pode ser mais fácil obter uma densidade de código mais alta do que em C ou assembler.
Aqui está a seguir para M16C .
fonte
Defina o nível de otimização do compilador. Muitos IDEs têm configurações que permitem otimizações de tamanho de código às custas do tempo de compilação (ou talvez até o tempo de processamento em alguns casos). Eles podem realizar a compactação de código reexecutando o otimizador algumas vezes, procurando padrões menos otimizáveis e uma série de truques que podem não ser necessários para a compilação casual / depuração. Normalmente, por padrão, os compiladores são configurados para um nível médio de otimização. Navegue pelas configurações e encontre uma escala de otimização baseada em números inteiros.
fonte
Se você já estiver usando um compilador de nível profissional como o IAR, acho que você terá dificuldades para obter economias sérias com pequenos ajustes de código de baixo nível - precisará procurar mais para remover a funcionalidade ou realizar grandes reescreve as peças de maneira mais eficiente. Você precisará ser um codificador mais inteligente do que quem escreveu a versão original ... Quanto à RAM, você precisa examinar muito bem como é usada atualmente e verificar se há espaço para sobrepor o uso da mesma RAM para coisas diferentes em momentos diferentes (os sindicatos são úteis para isso). Os tamanhos padrão de pilha e pilha do IAR nos modelos ARM / AVR que eu costumava ser excessivamente generosos, portanto, essa seria a primeira coisa a considerar.
fonte
Alguma outra coisa para verificar - alguns compiladores em algumas arquiteturas copiam constantes para a RAM - normalmente usados quando o acesso a constantes flash é lento / difícil (por exemplo, AVR), por exemplo, o compilador AVR do IAR exige que um qualificador _ _flash não copie uma constante para a RAM)
fonte
Se o seu processador não tiver suporte de hardware para uma pilha de parâmetro / local, mas o compilador tentar implementar uma pilha de parâmetros em tempo de execução de qualquer maneira, e se seu código não precisar ser reentrante, você poderá salvar o código espaço alocando estaticamente variáveis automáticas. Em alguns casos, isso deve ser feito manualmente; em outros casos, as diretivas do compilador podem fazê-lo. A alocação manual eficiente exigirá o compartilhamento de variáveis entre as rotinas. Esse compartilhamento deve ser feito com cuidado, para garantir que nenhuma rotina use uma variável que outra rotina considere "dentro do escopo", mas, em alguns casos, os benefícios do tamanho do código podem ser significativos.
Alguns processadores possuem convenções de chamada que podem tornar alguns estilos de passagem de parâmetro mais eficientes que outros. Por exemplo, nos controladores PIC18, se uma rotina usa um único parâmetro de um byte, ela pode ser passada em um registro; se for necessário mais do que isso, todos os parâmetros devem ser passados na RAM. Se uma rotina usar dois parâmetros de um byte, pode ser mais eficiente "passar" um em uma variável global e depois passar o outro como parâmetro. Com rotinas amplamente usadas, a economia pode aumentar. Eles podem ser especialmente significativos se o parâmetro passado via global for um sinalizador de bit único ou se normalmente terá um valor de 0 ou 255 (já que existem instruções especiais para armazenar um 0 ou 255 na RAM).
No ARM, colocar variáveis globais que são frequentemente usadas juntas em uma estrutura pode reduzir significativamente o tamanho do código e melhorar o desempenho. Se A, B, C, D e E são variáveis globais separadas, o código que usa todos eles deve carregar o endereço de cada um em um registro; se não houver registros suficientes, pode ser necessário recarregar esses endereços várias vezes. Por outro lado, se eles fazem parte da mesma estrutura global MyStuff, o código que usa MyStuff.A, MyStuff.B etc. pode simplesmente carregar o endereço do MyStuff uma vez. Grande vitória.
fonte
1.Se o seu código depende de muitas estruturas, verifique se os membros da estrutura estão ordenados entre aqueles que ocupam mais memória e menos.
Ex: "uint32_t uint16_t uint8_t" em vez de "uint16_t uint8_t uint32_t"
Isso garantirá o preenchimento mínimo da estrutura.
2.Use const para variáveis, quando aplicável. Isso garantirá que essas variáveis estejam na ROM e não consumam RAM
fonte
Alguns truques (talvez óbvios) que usei com êxito para compactar o código de algum cliente:
Compactar sinalizadores em campos de bits ou máscaras de bits. Isso pode ser benéfico, pois geralmente os booleanos são armazenados como números inteiros, desperdiçando memória. Isso economizará RAM e ROM e geralmente não é feito pelo compilador.
Procure redundância no código e use loops ou funções para executar instruções repetidas.
Também salvei alguma ROM substituindo muitas
if(x==enum_entry) <assignment>
instruções de constantes por uma matriz indexada, cuidando para que as entradas enum pudessem ser usadas como o índice da matrizfonte
Se possível, use funções embutidas ou macros do compilador em vez de pequenas funções. Há sobrecarga de tamanho e velocidade com a passagem de argumentos e outros que podem ser remediados com a função embutida.
fonte
int get_a(struct x) {return x.a;}
Mude as variáveis locais para o mesmo tamanho dos seus registros de CPU.
Se a CPU for de 32 bits, use variáveis de 32 bits, mesmo que o valor máximo nunca fique acima de 255. Se você usou uma variável de 8 bits, o compilador adicionará código para mascarar os 24 bits superiores.
O primeiro lugar que eu procuraria é nas variáveis de loop for.
Pode parecer um bom lugar para uma variável de 8 bits, mas uma variável de 32 bits pode produzir menos código.
fonte