Come spremere il codice per più Flash e RAM? [chiuso]


14

Ho lavorato allo sviluppo di una funzione su un nostro prodotto particolare. C'è stata una richiesta per il porting della stessa funzione su un altro prodotto. Questo prodotto si basa su un microcontrollore M16C, che tradizionalmente ha 64 KB di Flash e 2 KB di RAM.

È un prodotto maturo e, pertanto, sono rimasti solo 132 byte di Flash e 2 byte di RAM.

Per eseguire il porting della funzione richiesta (la funzione stessa è stata ottimizzata), ho bisogno di 1400 byte di Flash e ~ 200 byte di RAM.

Qualcuno ha qualche suggerimento su come recuperare questi byte dalla compattazione del codice? Quali cose specifiche cerco quando cerco di compattare il codice di lavoro già esistente?

Qualsiasi idea sarà davvero apprezzata.

Grazie.


1
Grazie a tutti per i suggerimenti. Vi terrò aggiornati con i miei progressi ed elencherò i passaggi che hanno funzionato e quelli che non lo hanno fatto.
IntelliChick,

Ok, ecco le cose che ho provato che hanno funzionato: Verso le versioni del compilatore. L'ottimizzazione è migliorata drasticamente, il che mi ha dato circa 2K di Flash. Sono passato attraverso i file dell'elenco per verificare la funzionalità ridondante e non utilizzata (ereditata a causa della base di codice comune) per il prodotto specifico e ho guadagnato un po 'più di Flash.
IntelliChick,

Per la RAM ho fatto quanto segue: Sono passato attraverso il file della mappa per controllare le funzioni / i moduli che utilizzavano la maggior parte della RAM. Ho trovato una funzione davvero pesante (12 canali, ciascuno con una quantità fissa di memoria allocata), di codice legacy, ho capito cosa stava cercando di ottenere e ottimizzato l'utilizzo della RAM, condividendo le informazioni tra i canali che erano comuni. Questo mi ha dato ~ 200 byte di cui avevo bisogno.
IntelliChick,

Se si dispone di file ASCII, è possibile utilizzare la compressione da 8 a 7 bit. Risparmia il 12,5%. L'uso di un file zip richiederebbe più codice per comprimerlo e decomprimerlo piuttosto che lasciarlo.
Sparky256,

Risposte:


18

Hai un paio di opzioni: in primo luogo è cercare il codice ridondante e spostarlo in una singola chiamata per eliminare la duplicazione; il secondo è rimuovere la funzionalità.

Dai un'occhiata al tuo file .map e vedi se ci sono funzioni che puoi eliminare o riscrivere. Assicurati anche che le chiamate in libreria utilizzate siano davvero necessarie.

Alcune cose come la divisione e le moltiplicazioni possono portare un sacco di codice, ma l'uso di turni e un migliore utilizzo delle costanti può ridurre il codice. Dai anche un'occhiata a cose come costanti di stringa e printfs. Ad esempio ognuno printfdivorerà la tua rom ma potresti essere in grado di avere un paio di stringhe di formato condivise invece di ripetere più volte quella costante di stringa.

Per la memoria, vedi se riesci a sbarazzarti dei globi e usa invece le auto in una funzione. Evita anche quante più variabili possibili nella funzione principale, in quanto consumano memoria proprio come fanno i globuli.


1
Grazie per i suggerimenti, posso sicuramente provarne molti, tranne quello per le costanti di stringa. È puramente un dispositivo incorporato, senza interfaccia utente e quindi non ci sono chiamate a printf () all'interno del codice. Spero che questi suggerimenti mi permettano di ottenere la mia memoria flash di 1400 byte / 200 byte di cui ho bisogno.
IntelliChick

1
@IntelliChick rimarrai stupito da quante persone usano printf () all'interno di un dispositivo incorporato per stampare per il debug o l'invio a una periferica. Sembra che tu lo sappia meglio di questo, ma se qualcuno ha scritto del codice sul progetto prima di te, non sarebbe male controllarlo.
Kellenjb,

5
E solo per espandere il mio commento precedente, rimarrai stupito anche da quante persone aggiungono dichiarazioni di debug, ma non le rimuovono mai. Anche le persone che fanno #ifdefs a volte diventano ancora pigre.
Kellenjb,

1
Fantastico, grazie! Ho ereditato questa base di codice, quindi sicuramente cercherò quelli. Vi terrò informati, sullo stato di avanzamento, e cercherò di tenere traccia di quanti byte di memoria o Flash ho guadagnato facendo cosa, proprio come riferimento per chiunque possa aver bisogno di farlo in futuro.
IntelliChick

Solo una domanda al riguardo: che dire della funzione nidificata chiama i salti da un livello all'altro. Quanto sovraccarico aggiunge? È meglio mantenere la modularità disponendo di più chiamate di funzione o ridurre le chiamate di funzione e salvare alcuni byte. Ed è significativo?
IntelliChick

8

Vale sempre la pena guardare l'output del file di elenco (assemblatore) per cercare cose che il tuo particolare compilatore non è particolarmente adatto.

Ad esempio, potresti scoprire che le variabili locali sono molto costose e se l'applicazione è abbastanza semplice da valere il rischio, spostare alcuni contatori di loop in variabili statiche potrebbe risparmiare molto codice.

O l'indicizzazione dell'array potrebbe essere molto costosa, ma le operazioni con i puntatori sono molto più economiche. O vice versa.

Ma guardare il linguaggio assembly è il primo passo.


3
È molto importante sapere cosa fa il tuo compilatore. Dovresti vedere che differenza c'è sul mio compilatore. Fa piangere i bambini (me compreso).
Kortuk,

8

Le ottimizzazioni del compilatore, ad esempio, -Osin GCC offrono il miglior equilibrio tra velocità e dimensioni del codice. Evita -O3, poiché potrebbe aumentare la dimensione del codice.


3
Se lo fai, dovrai ripetere il test TUTTO! Le ottimizzazioni possono impedire il funzionamento del codice di lavoro a causa di nuove ipotesi fatte dal compilatore.
Robert,

@Robert, questo è vero solo se si usano istruzioni non definite: ad es. A = a ++ verrà compilato diversamente in -O0 e -O3.
Thomas O

5
@Thomas non è vero. Se hai un ciclo for per ritardare i cicli di clock, molti ottimizzatori realizzeranno che non ci fai nulla e lo rimuovono. Questo è solo un esempio.
Kellenjb,

1
@thomas O, devi anche assicurarti di stare attento alle definizioni delle funzioni volatili. Gli ottimizzatori faranno esplodere quelli che pensano di conoscere bene C ma non comprendono la complessità delle operazioni atomiche.
Kortuk,

1
Tutti i punti positivi. Le funzioni / variabili volatili, per definizione, NON devono essere ottimizzate. Qualsiasi ottimizzatore che esegue ottimizzazioni su tale (incluso il tempo di chiamata e inline) è rotto.
Thomas O

8

Per la RAM, controlla l'intervallo di tutte le tue variabili: stai usando ints dove potresti usare un carattere? I buffer sono più grandi di quanto devono essere?

La compressione del codice dipende molto dall'applicazione e dallo stile di programmazione. I tuoi importi lasciati suggeriscono che forse il codice è già andato via anche se un po 'di compressione, il che potrebbe significare che c'è ancora poco da avere.

Dai anche una occhiata alla funzionalità generale: c'è qualcosa che non è realmente utilizzato e può essere rimosso?


8

Se si tratta di un vecchio progetto ma il compilatore è stato sviluppato da allora, è possibile che un compilatore più recente possa produrre codice più piccolo


Grazie Mike! L'ho provato in passato e lo spazio guadagnato è già stato utilizzato! :) Passato dal compilatore IAR C 3.21d a 3.40.
IntelliChick

1
Ho spostato su un'altra versione e sono riuscito a ottenere un po 'più di Flash per adattarsi alla funzione. Sono davvero alle prese con la RAM, che è rimasta invariata. :(
IntelliChick

7

Vale sempre la pena controllare il manuale del compilatore per le opzioni per ottimizzare lo spazio.

Per gcc -ffunction-sectionse -fdata-sectionscon il--gc-sections flag linker sono buoni per rimuovere il codice morto.

Ecco alcuni altri suggerimenti eccellenti (orientati verso AVR)


Funziona davvero? I documenti dicono "Quando specifichi queste opzioni, l'assemblatore e il linker creeranno oggetti più grandi e file eseguibili e saranno anche più lenti." Capisco che avere sezioni separate abbia senso per un micro con sezioni Flash e RAM - Questa affermazione nei documenti non è applicabile ai microcontrollori?
Kevin Vermeer,

La mia esperienza è che funziona bene per AVR
Toby Jaffey l'

1
Questo non funziona bene nella maggior parte dei compilatori che ho usato. È come usare la parola chiave register. Puoi dire al compilatore che una variabile va in un registro, ma un buon ottimizzatore lo farà molto meglio di un essere umano (argomenta come alcuni potrebbero, in pratica, non è accettabile farlo).
Kortuk,

1
Quando inizi ad assegnare posizioni, stai costringendo il compilatore a posizionare le cose in determinate posizioni, molto importante per il codice di bootloader avanzato, ma è terribile da gestire nell'ottimizzatore, mentre prendi delle decisioni per te stai portando via un passo di ottimizzazione potrebbe fare. In alcuni compilatori lo progettano per avere sezioni per cui viene utilizzato il codice, questo è il caso di dire al compilatore più informazioni per capire il tuo uso, questo ti aiuterà. Se il compilatore non lo suggerisce, non farlo.
Kortuk,

6

È possibile esaminare la quantità di spazio stack e spazio heap allocati. Potresti essere in grado di recuperare una notevole quantità di RAM se uno o entrambi di questi sono sovrallocati.

La mia ipotesi è per un progetto che si inserisce in 2k di RAM per cominciare non c'è allocazione dinamica della memoria (uso di malloc, calloce così via). In questo caso è possibile eliminare del tutto l'heap supponendo che l'autore originale abbia lasciato un po 'di RAM allocata per l'heap.

Devi stare molto attento a ridurre le dimensioni dello stack in quanto ciò può causare bug che sono molto difficili da trovare. Potrebbe essere utile iniziare inizializzando l'intero spazio dello stack su un valore noto (qualcosa di diverso da 0x00 o 0xff poiché questi valori si verificano già comunemente), quindi eseguire il sistema per un po 'per vedere quanto spazio dello stack è inutilizzato.


Queste sono ottime scelte. Noterò che non dovresti mai usare malloc in un sistema incorporato.
Kortuk,

1
@Kortuk Dipende dalla tua definizione di embedded e
dall'attività

1
@joby, sì, lo capisco. In un sistema con 0 riavvia e l'assenza di un sistema operativo come Linux, Malloc può essere molto male.
Kortuk,

Non esiste allocazione dinamica della memoria, nessun luogo in cui viene utilizzato malloc, calloc. Ho anche controllato l'allocazione dell'heap ed è già stata impostata su 0, quindi non c'è allocazione dell'heap. La dimensione dello stack attualmente allocata è di 254 byte e la dimensione dello stack di interrupt in 128 byte.
IntelliChick

5

Il tuo codice usa la matematica in virgola mobile? Potresti essere in grado di implementare nuovamente i tuoi algoritmi utilizzando solo la matematica dei numeri interi ed eliminare i costi generali dell'utilizzo della libreria in virgola mobile C. Ad esempio in alcune applicazioni, funzioni come seno, log, exp possono essere sostituite da approssimazioni polinomiali intere.

Il tuo codice utilizza tabelle di ricerca di grandi dimensioni per eventuali algoritmi, come i calcoli CRC? Puoi provare a sostituire una versione diversa dell'algoritmo che calcola i valori al volo, invece di utilizzare le tabelle di ricerca. L'avvertenza è che l'algoritmo più piccolo è molto probabilmente più lento, quindi assicurati di avere abbastanza cicli della CPU.

Il tuo codice contiene grandi quantità di dati costanti, come tabelle di stringhe, pagine HTML o grafica pixel (icone)? Se è abbastanza grande (diciamo 10 kB), potrebbe valere la pena implementare uno schema di compressione molto semplice per ridurre i dati e decomprimerli al volo quando necessario.


Ci sono 2 piccole tabelle di ricerca, nessuna delle quali purtroppo ammonterà a 10K. E nemmeno la matematica in virgola mobile viene utilizzata. :( Grazie per i suggerimenti però. Sono buoni.
IntelliChick

2

Puoi provare a riorganizzare il codice molto, in uno stile più compatto. Dipende molto da cosa sta facendo il codice. La chiave è trovare cose simili e implementarle nuovamente l'una nell'altra. Un estremo sarebbe usare un linguaggio di livello superiore, come Forth, con il quale può essere più facile ottenere una densità di codice maggiore rispetto a C o assembler.

Ecco Forth per M16C .


2

Imposta il livello di ottimizzazione del compilatore. Molti IDE hanno impostazioni che consentono di ottimizzare le dimensioni del codice a spese del tempo di compilazione (o forse anche del tempo di elaborazione in alcuni casi). Possono eseguire la compattazione del codice rieseguendo il proprio ottimizzatore un paio di volte, cercando modelli meno comuni e ottimizzabili e tutta un'altra serie di trucchi che potrebbero non essere necessari per la compilazione casuale / di debug. Di solito, per impostazione predefinita, i compilatori sono impostati su un livello medio di ottimizzazione. Cerca nelle impostazioni e dovresti essere in grado di trovare una scala di ottimizzazione basata su numeri interi.


1
Attualmente ottimizzato al massimo per dimensioni. :) Grazie per il suggerimento però. :)
IntelliChick

2

Se stai già utilizzando un compilatore di livello professionale come IAR, penso che avrai difficoltà a ottenere seri risparmi da una modifica del codice di basso livello - dovrai guardare di più verso la rimozione di funzionalità o fare importanti riscrive le parti in modo più efficiente. Dovrai essere un programmatore più intelligente di chiunque abbia scritto la versione originale ... Per quanto riguarda la RAM, devi dare uno sguardo molto duro al modo in cui viene attualmente utilizzato e vedere se c'è spazio per sovrapporre l'utilizzo della stessa RAM per cose diverse in momenti diversi (i sindacati sono utili per questo). Le dimensioni di heap e stack predefinite di IAR in quelle ARM / AVR che ho avuto la tendenza ad essere eccessivamente generose, quindi queste sarebbero le prime cose da guardare.


Grazie Mike. Il codice utilizza già i sindacati nella maggior parte dei posti, ma darò un'occhiata ad altri posti, dove questo potrebbe ancora aiutare. Vedrò anche la dimensione dello stack scelta e vedrò se può essere ottimizzato.
IntelliChick

Come faccio a sapere quale dimensione dello stack è appropriata?
IntelliChick

2

Qualcos'altro da controllare - alcuni compilatori su alcune architetture copiano le costanti nella RAM - in genere utilizzato quando l'accesso alle costanti flash è lento / difficile (ad es. AVR) ad es. Il compilatore IAR AVR richiede una qualifica _ _flash per non copiare una costante in RAM)


Grazie Mike. Sì, l'avevo già verificato: si chiamava l'opzione "Writeable constants" per il compilatore IAR C M16C. Copia le costanti dalla ROM alla RAM. Questa opzione non è selezionata per il mio progetto. Ma un controllo davvero valido! Grazie.
IntelliChick

1

Se il tuo processore non ha il supporto hardware per uno stack di parametri / locale ma il compilatore tenta comunque di implementare uno stack di parametri di runtime e se il tuo codice non deve essere reinserito, potresti essere in grado di salvarlo spazio allocando staticamente le variabili automatiche. In alcuni casi, questo deve essere fatto manualmente; in altri casi, le direttive del compilatore possono farlo. Un'allocazione manuale efficiente richiederà la condivisione di variabili tra le routine. Tale condivisione deve essere effettuata con attenzione, per garantire che nessuna routine utilizzi una variabile che un'altra routine considera "nell'ambito", ma in alcuni casi i vantaggi della dimensione del codice possono essere significativi.

Alcuni processori hanno convenzioni di chiamata che possono rendere alcuni stili di passaggio dei parametri più efficienti di altri. Ad esempio, sui controller PIC18, se una routine accetta un singolo parametro a un byte, può essere passata in un registro; se ci vuole più di questo, tutti i parametri devono essere passati nella RAM. Se una routine accetta due parametri a un byte, può essere più efficiente "passare" uno in una variabile globale, quindi passare l'altro come parametro. Con routine ampiamente utilizzate, i risparmi possono sommarsi. Possono essere particolarmente significativi se il parametro passato tramite global è un flag a bit singolo o se di solito avrà un valore di 0 o 255 (poiché esistono istruzioni speciali per memorizzare uno 0 o 255 in RAM).

Sul ARM, l'inserimento di variabili globali che vengono spesso utilizzate insieme in una struttura può ridurre significativamente la dimensione del codice e migliorare le prestazioni. Se A, B, C, D ed E sono variabili globali separate, il codice che le utilizza tutte deve caricare l'indirizzo di ciascuna in un registro; se non ci sono abbastanza registri, potrebbe essere necessario ricaricare quegli indirizzi più volte. Al contrario, se fanno parte della stessa struttura globale MyStuff, il codice che utilizza MyStuff.A, MyStuff.B, ecc. Può semplicemente caricare l'indirizzo di MyStuff una volta. Grande vincita.


1

1.Se il codice si basa su molte strutture, assicurarsi che i membri della struttura siano ordinati da quelli che occupano la maggior parte della memoria al minimo.

Esempio: "uint32_t uint16_t uint8_t" invece di "uint16_t uint8_t uint32_t"

Ciò garantirà un'imbottitura minima della struttura.

2. Utilizzare const per le variabili, ove applicabile. Ciò garantirà che tali variabili siano nella ROM e non consumino RAM


1

Alcuni trucchi (forse ovvi) che ho usato con successo nel comprimere il codice di alcuni clienti:

  1. Compatta flag in campi bit o maschere bit. Questo può essere utile poiché di solito i booleani sono memorizzati come numeri interi, sprecando così la memoria. Ciò salverà sia la RAM che la ROM e di solito non viene eseguita dal compilatore.

  2. Cerca la ridondanza nel codice e usa loop o funzioni per eseguire istruzioni ripetute.

  3. Ho anche salvato un po 'di ROM sostituendo molte if(x==enum_entry) <assignment>istruzioni dalle costanti con un array indicizzato, avendo cura che le voci enum potessero essere usate come indice dell'array


0

Se possibile, utilizzare le funzioni incorporate o le macro del compilatore anziché le piccole funzioni. Ci sono overhead di dimensioni e velocità con argomenti di passaggio e simili che possono essere risolti rendendo la funzione in linea.


1
Qualsiasi compilatore decente dovrebbe farlo automaticamente per le funzioni chiamate una sola volta.
mikeselectricstuff,

5
Ho scoperto che l'allineamento di solito è più utile per l'ottimizzazione della velocità e di solito a costo di dimensioni maggiori.
Craig McQueen,

in linea di solito aumenterà la dimensione del codice, tranne che con funzioni banali comeint get_a(struct x) {return x.a;}
Dmitry Grigoryev,

0

Modificare le variabili locali in modo che abbiano le stesse dimensioni dei registri della CPU.

Se la CPU è a 32 bit, utilizzare le variabili a 32 bit anche se il valore massimo non supererà mai 255. Ho usato una variabile a 8 bit, il compilatore aggiungerà il codice per mascherare i 24 bit superiori.

Il primo posto che vorrei guardare è le variabili for-loop.

for( i = 0; i < 100; i++ )

Questo potrebbe sembrare un buon posto per una variabile a 8 bit, ma una variabile a 32 bit potrebbe produrre meno codice.


Ciò potrebbe salvare il codice ma consumerà RAM.
mikeselectricstuff

Mangia RAM solo se quella chiamata di funzione si trova nel ramo più lungo della traccia della chiamata. Altrimenti, sta riutilizzando lo spazio dello stack di cui già necessita qualche altra funzione.
Robert,

2
Di solito vero, purché sia ​​una variabile locale. Se a corto di RAM, la dimensione dei var globali, in particolare gli array, è un buon punto di partenza per cercare risparmi.
mikeselectricstuff,

1
Un'altra possibilità, interessante, è quella di sostituire le variabili senza segno con quelle con segno. Se un compilatore ottimizza un corto senza segno in un registro a 32 bit, deve aggiungere il codice per assicurarsi che il suo valore sia compreso tra 65535 e zero. Se, tuttavia, il compilatore ottimizza un short firmato a un registro, non è richiesto tale codice. Poiché non esiste alcuna garanzia che cosa accadrà se un cortocircuito viene incrementato oltre 32767, i compilatori non sono tenuti ad emettere codice per gestirlo. Su almeno due compilatori ARM che ho visto, il codice abbreviato firmato può essere più piccolo del codice abbreviato per tale motivo.
supercat
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.