Processori / clock più veloci possono eseguire più codice?


9

Sto scrivendo un programma per l'esecuzione su un ATmega 328 che funziona a 16Mhz (è un Arduino Duemilanove se li conosci, è un chip AVR).

Ho un processo di interruzione in esecuzione ogni 100 microsecondi. È impossibile, direi, capire quanto "codice" è possibile eseguire in un ciclo di 100 microsecondi (sto scrivendo in C che presumibilmente viene convertito in assembly e quindi in un'immagine binaria?).

Inoltre, ciò dipenderebbe dalla complessità del codice (ad esempio un liner gigante potrebbe essere più lento rispetto a diverse linee brevi, ad esempio).

La mia comprensione è corretta, in quanto il mio processore con una frequenza di clock o 16Mhz esegue 16 milioni di cicli al secondo (ciò significa 16 cicli per microsecondo 16.000.000 / 1.000 / 1.000); E quindi, se voglio fare di più nel mio ciclo da 100 microsecondi, acquistare un modello più veloce come una versione a 72 Mhz mi darebbe 72 cicli per microsecondo (72.000.000 / 1.000 / 1.000)?

Attualmente funziona un po 'troppo lentamente, cioè impiega un po' più di 100 microsecondi per eseguire il ciclo (per quanto tempo è esattamente troppo difficile da dire, ma graduale rimane indietro) e vorrei che facesse un po 'di più, è questo è un approccio sano ottenere un chip più veloce o sono impazzito?


.... Un ATmega328 NON è un chip ARM. È un AVR.
vicatcu,

Saluti, corretto!
jwbensley,

Risposte:


9

In generale, il numero di istruzioni di assemblaggio che il dispositivo può eseguire al secondo dipenderà dal mix di istruzioni e dal numero di cicli necessari per l'esecuzione di ciascun tipo di istruzione (CPI). In teoria è possibile contare il codice del ciclo osservando il file asm disassemblato e osservando la funzione di cui si è preoccupati, contando tutti i diversi tipi di istruzioni in esso e cercando i conteggi del ciclo dal foglio dati per il proprio processore di destinazione.

Il problema di determinare il numero effettivo di istruzioni al secondo è aggravato dai processori più complessi dal fatto che sono pipeline e hanno cache e cosa no. Questo non è il caso di un dispositivo semplice come un ATMega328 che è una singola istruzione nel processore di volo.

Per quanto riguarda le questioni pratiche, per un dispositivo semplice come un AVR, la mia risposta sarebbe più o meno "sì". Raddoppiare la velocità di clock dovrebbe dimezzare il tempo di esecuzione di una determinata funzione. Per un AVR, tuttavia, non funzioneranno più velocemente di 20MHz, quindi potresti solo "overcloccare" il tuo Arduino di un altro 4MHz.

Questo consiglio non si generalizza a un processore che ha funzionalità più avanzate. Raddoppiando la velocità di clock sul tuo processore Intel, in pratica non raddoppierà il numero di istruzioni che esegue al secondo (a causa di errate previsioni del ramo, errori nella cache e così via).


Ciao, grazie per la tua risposta informativa! Ho visto uno di questi ( coolcomponents.co.uk/catalog/product_info.php?products_id=808 ), hai detto che un AVR non può andare più veloce di 20Mhz, perché? Il chip sulla scheda di cui sopra ( uk.farnell.com/stmicroelectronics/stm32f103rbt6/… ) è un ARM da 72 MHz, posso aspettarmi un ragionevole aumento delle prestazioni da questo nel modo che ho descritto sopra?
jwbensley,

2
Raddoppiare la velocità di elaborazione potrebbe non aumentare la velocità di trasmissione delle istruzioni poiché potresti iniziare a superare la velocità con cui è possibile recuperare le istruzioni dal flash. A questo punto inizi a colpire "Stati di attesa flash" in cui la CPU si ferma mentre attende che le istruzioni arrivino dal flash. Alcuni microcontrollori aggirano questo problema consentendo di eseguire il codice dalla RAM che è molto più veloce di FLASH.
Majenko,

@Majenko: divertente, entrambi abbiamo fatto lo stesso punto allo stesso tempo.
Jason S,

Succede ... il tuo è meglio del mio :)
Majenko,

1
OK, ho contrassegnato la risposta di Vicatcu come "la risposta". Sento che era il più appropriato per quanto riguarda la mia domanda originale sulla velocità relativa alle prestazioni, sebbene tutte le risposte siano fantastiche e sono davvero pieno di risposte di tutti. Mi hanno dimostrato che si tratta di un argomento più ampio di quanto non avessi
mai pensato

8

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) % 4a 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) & 3per 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!


buoni consigli sulle tecniche di misurazione del tempo di esecuzione empirica
vicatcu,

Un'altra ottima risposta per la mia domanda, grazie mille Jason S per questo fantastico pezzo di conoscenza! Due cose apparenti dopo aver letto questo; In primo luogo, posso aumentare l'interrupt da ogni 100 uS a 500 uS per dare al codice più tempo per l'esecuzione, mi rendo conto che ora questo non mi sta davvero avvantaggiando così velocemente. In secondo luogo, penso che il mio codice sia forse troppo inefficiente, con un tempo di interruzione più lungo e un codice migliore potrebbe andare tutto bene. Stackoverflow è un posto migliore per pubblicare il codice, quindi lo pubblicherò lì e inserirò un link qui, se qualcuno vuole dare un'occhiata e dare consigli, si prega di fare: D
jwbensley

5

Un'altra cosa da notare: probabilmente ci sono alcune ottimizzazioni che puoi eseguire per rendere il tuo codice più efficiente.

Ad esempio: ho una routine che parte dall'interruzione del timer. La routine deve essere completata entro 52µS e deve passare attraverso una grande quantità di memoria mentre lo sta facendo.

Ho gestito un grande aumento di velocità bloccando la variabile del contatore principale su un registro con (sul mio µC e compilatore - diverso per il tuo):

register unsigned int pointer asm("W9");

Non conosco il formato per il tuo compilatore - RTFM, ma ci sarà qualcosa che puoi fare per rendere più veloce la tua routine senza dover passare all'assemblaggio.

Detto questo, probabilmente puoi fare un lavoro molto migliore nell'ottimizzare la tua routine rispetto al compilatore, quindi passare all'assemblaggio può darti alcuni enormi aumenti di velocità.


lol I "simultaneamente" ha commentato la mia risposta sulla messa a punto dell'assemblatore e sull'allocazione dei registri :)
vicatcu,

Se sta prendendo 100us su un processore a 16 MHz - è ovviamente piuttosto enorme, quindi è un sacco di codice da ottimizzare. Ho sentito che oggi i compilatori producono circa 1,1 volte il codice rispetto all'assemblaggio scelto a mano. Non ne vale assolutamente la pena per una routine così grande. Per la rasatura del 20% di sconto su una funzione a 6 righe, forse ...
Defenestration,

1
Non necessariamente ... Potrebbero essere solo 5 righe di codice in un ciclo. E non si tratta della dimensione del codice ma dell'efficienza del codice . Potresti essere in grado di scrivere il codice in modo diverso facendolo funzionare più velocemente. So per la mia routine di interrupt che ho fatto. Ad esempio, sacrificando le dimensioni per la velocità. Eseguendo lo stesso codice 10 volte in sequenza si risparmia il tempo di avere il codice per eseguire il ciclo e le variabili contatore associate. Sì, il codice è 10 volte più lungo, ma funziona più velocemente.
Majenko,

Ciao Majenko, non conosco il montaggio ma stavo pensando di impararlo, e pensavo che l'Arduino sarebbe stato meno complicato del mio computer desktop, quindi questo potrebbe essere un buon momento per imparare, specialmente per quello che voglio sapere più su quello che sta succedendo e un livello inferiore. Come altri hanno già detto, non riscriverei il tutto solo in determinate parti. La mia comprensione è che posso entrare e uscire da ASM in C, è corretto, è così che si potrebbe ottenere questo mix di C e ASM? Pubblicherò su StackOverflow per i dettagli, subito dopo un'idea generale.
jwbensley,

@javano: Sì. Puoi entrare e uscire da ASM in C. Molti sistemi embedded sono stati scritti in questo modo - in una miscela di C e assembly - principalmente perché c'erano alcune cose che semplicemente non potevano essere fatte nei compilatori C primitivi disponibili presso tempo. Tuttavia, i moderni compilatori C come gcc (che è il compilatore utilizzato da Arduino) ora gestiscono la maggior parte e in molti casi tutte le cose che richiedevano il linguaggio assembly.
davidcary,
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.