La risposta di @ vicatcu è piuttosto completa. Un'ulteriore cosa da notare è che la CPU può incorrere in stati di attesa (cicli di CPU in stallo) quando si accede all'I / O, inclusa la memoria di programma e dati.
Ad esempio, stiamo usando un DSP TI F28335; alcune aree della RAM sono 0-wait state per memoria di programma e dati, quindi quando si esegue il codice nella RAM, viene eseguito a 1 ciclo per istruzione (ad eccezione di quelle istruzioni che richiedono più di 1 ciclo). Quando si esegue il codice dalla memoria FLASH (EEPROM integrata, più o meno), tuttavia, non può funzionare a 150MHz completi ed è più volte più lento.
Per quanto riguarda il codice di interruzione ad alta velocità, è necessario apprendere alcune cose.
Innanzitutto, acquisisci familiarità con il tuo compilatore. Se il compilatore fa un buon lavoro, non dovrebbe essere molto più lento dell'assemblaggio codificato a mano per la maggior parte delle cose. (dove "molto più lento": un fattore 2 andrebbe bene per me; un fattore 10 sarebbe inaccettabile) Devi imparare come (e quando) usare i flag di ottimizzazione del compilatore e ogni tanto dovresti cercare all'output del compilatore per vedere come funziona.
Alcune altre cose che puoi fare fare al compilatore per velocizzare il codice:
usa le funzioni inline (non ricordo se C supporta questo o se è solo un C ++), sia per le piccole funzioni che per quelle che verranno eseguite solo una o due volte. L'aspetto negativo è che le funzioni in linea sono difficili da eseguire il debug, soprattutto se l'ottimizzazione del compilatore è attivata. Ma ti salvano inutili sequenze di chiamata / ritorno, specialmente se l'astrazione della "funzione" è per scopi di progettazione concettuale piuttosto che per l'implementazione del codice.
Guarda il manuale del tuo compilatore per vedere se ha funzioni intrinseche: si tratta di funzioni integrate dipendenti dal compilatore che si associano direttamente alle istruzioni di assemblaggio del processore; alcuni processori hanno istruzioni di assemblaggio che fanno cose utili come min / max / bit reverse e puoi risparmiare tempo.
Se stai eseguendo un calcolo numerico, assicurati di non chiamare inutilmente le funzioni della libreria matematica. Abbiamo avuto un caso in cui il codice era simile y = (y+1) % 4
a un contatore che aveva un periodo di 4, in attesa che il compilatore implementasse il modulo 4 come un bit-AND. Invece ha chiamato la libreria matematica. Quindi abbiamo sostituito con y = (y+1) & 3
per fare quello che volevamo.
Acquisisci familiarità con la pagina degli hack bit-twiddling . Ti garantisco che utilizzerai almeno uno di questi spesso.
Dovresti anche usare le periferiche timer della tua CPU per misurare il tempo di esecuzione del codice - la maggior parte di esse ha un timer / contatore che può essere impostato per funzionare alla frequenza di clock della CPU. Cattura una copia del contatore all'inizio e alla fine del tuo codice critico e puoi vedere quanto tempo impiega. Se non riesci a farlo, un'altra alternativa è quella di abbassare un pin di uscita all'inizio del tuo codice, e alzarlo alla fine, e guardare questo output su un oscilloscopio per cronometrare l'esecuzione. Esistono dei compromessi per ciascun approccio: il timer / contatore interno è più flessibile (puoi cronometrare più cose) ma più difficile ottenere le informazioni, mentre l'impostazione / cancellazione di un pin di output è immediatamente visibile su un ambito e puoi acquisire statistiche, ma è difficile distinguere più eventi.
Infine, esiste un'abilità molto importante che deriva dall'esperienza, sia generale che con specifiche combinazioni processore / compilatore: sapere quando e quando non ottimizzare . In generale la risposta è non ottimizzare. La citazione di Donald Knuth viene pubblicata frequentemente su StackOverflow (di solito solo l'ultima parte):
Dobbiamo dimenticare le piccole efficienze, diciamo circa il 97% delle volte: l'ottimizzazione prematura è la radice di tutti i mali
Ma ti trovi in una situazione in cui sai che devi fare una sorta di ottimizzazione, quindi è tempo di mordere il proiettile e ottimizzare (o ottenere un processore più veloce, o entrambi). Non NON scrivere tutta l'ISR in assemblea. Questo è quasi un disastro garantito: se lo fai, entro mesi o addirittura settimane dimenticherai parti di ciò che hai fatto e perché, e il codice sarà probabilmente molto fragile e difficile da cambiare. È probabile che ci siano parti del codice che sono buoni candidati per l'assemblaggio.
Indica che parti del codice sono adatte per la codifica di assiemi:
- funzioni che sono piccole routine ben contenute e ben definite che difficilmente cambieranno
- funzioni che possono utilizzare specifiche istruzioni di montaggio (min / max / spostamento a destra / ecc.)
- funzioni che vengono chiamate molte volte (ti dà un moltiplicatore: se salvi 0,5usec per ogni chiamata e viene chiamato 10 volte, ti fa risparmiare 5 usec che è significativo nel tuo caso)
Impara la funzione del tuo compilatore chiamando convenzioni (ad es. Dove inserisce gli argomenti nei registri e quali registri salva / ripristina) in modo da poter scrivere routine di assemblaggio richiamabili in C.
Nel mio progetto attuale, abbiamo una base di codice piuttosto grande con un codice critico che deve essere eseguito in un interrupt a 10kHz (100usec - suona familiare?) E non ci sono molte funzioni scritte in assembly. Quelli che sono, sono cose come il calcolo CRC, le code del software, la compensazione guadagno / offset ADC.
In bocca al lupo!