Stima del carico della CPU di un'utilità di pianificazione

Aug 23 2020

Prima di tutto, dovrei notare che non sono sicuro di quali tag si applichino meglio qui, e non riesco a trovarne nessuno che si riferisca agli scheduler, ecc. Per favore informami se non sono quelli giusti.

Ho un programmatore di attività semplice e portatile (si basa solo su millis () e funziona su diversi sistemi supportati da Arduino) che viene chiamato tramite una funzione in loop () e che seleziona e quindi esegue un puntatore a funzione (il "task" ) da un elenco di attività con informazioni su stato, sospensione e priorità.

Vorrei avere un modo per determinare e dire all'utente quanto tempo della CPU viene speso per eseguire attività rispetto all'essere inattivo.

Il problema è che possono esserci altre funzioni chiamate da loop(), non solo il mio scheduler. Sarebbe quindi una cattiva idea tenere traccia del tempo totale rispetto al tempo trascorso in un'attività. Se lo facessi, l'utilizzo della CPU sarebbe sempre basso, poiché il tempo effettivo dell'attività non può utilizzare tutto il tempo della CPU poiché le altre attività in loop() ne userebbero tranquillamente una parte.

Tuttavia, se tengo traccia in modo specifico del tempo nell'utilità di pianificazione rispetto al tempo trascorso nell'attività, mi imbatto in un nuovo problema. Userei più memoria (qualche byte in più, ma ne sto già usando molta...) e più CPU*, dal momento che dovrei tenere traccia del totale finora e anche del tempo di esecuzione corrente per quell'iterazione di il pianificatore. Questo è in cima al costrutto simile che determina il tempo di esecuzione dell'attività. Principalmente, però, il problema è che la funzione loop() e lo scheduler funzionano abbastanza velocemente che il timer millis() può ticchettare più lentamente del passaggio tra il mio scheduler e la funzione loop(). Ciò fa sì che anche il timing sia disattivato, poiché verrebbe conteggiato se spunta nello scheduler anche se fosse principalmente colpa di loop().

Esiste una metodologia generale per calcolare il tempo della CPU? Sarebbe difficile suddividere lo scheduler nel tempo, e comunque funzionerebbe più lentamente. Potrei contare la percentuale di chiamate dello scheduler che eseguono/non eseguono attività, ma ciò non funzionerà perché le attività richiedono più tempo del no-op che si verifica se un'attività non viene eseguita e non tiene conto della durata dell'attività .

*Su un sistema AVR, la CPU a 8 bit utilizza un tempo aggregato sorprendente su tutte quelle operazioni a 32 bit, tra i calcoli del tempo di sospensione (solo sulle attività in sospensione, per essere onesti) e i calcoli del tempo trascorso inseriti nella mia precedente CPU tentativo di utilizzo. Non è stata l'unica causa, ma ho visto un bel po 'di tempo scivolare su un tentativo di codificare lo scheduler a causa di un problema simile.

Risposte

1 Majenko Aug 23 2020 at 16:49

L'unica metrica che hai è il tempo. L'unica cosa che puoi fare è confrontare quel tempo su iterazioni / chiamate di attività. L'approccio tipico è annotare l'ora all'inizio di un "tick" di un'attività e annotarla alla fine. Quindi aggiungi la differenza al totale per quell'attività.

Quando si desidera esaminare le percentuali di utilizzo, è possibile confrontare tali totali con il tempo totale di funzionamento del sistema.

Il runtime totale è già terminato per te, sotto forma di millis(), quindi non devi preoccuparti di questo.

La registrazione del tempo di esecuzione totale di ciascuna attività in microsecondi è abbastanza semplice, ma ovviamente comporterà un sovraccarico. Ciò è inevitabile, indipendentemente dalla strategia adottata. È una specie di quanto: l'atto di misurare cambia i risultati.

Se tratti la tua attività "inattiva" come qualsiasi altra attività (che è il modo normale di fare le cose - l'attività inattiva è solo un'attività che non fa nulla) allora la somma di tutto il tempo conta per tutte le attività = (entro una certa granularità) il tempo totale di esecuzione dell'utilità di pianificazione. La differenza tra questo e il runtime di sistema ( millis()) è la proporzione del tempo in cui il tuo scheduler è stato in esecuzione rispetto ad altre cose che accadono in loop().

Se confronti con millis()o micros()per il runtime di sistema dipende da te. Memorizzando micros()per attività ottieni una risoluzione ragionevole per il tempo di esecuzione di ciascuna attività (ma con un limite a quanto tempo prima che si concluda, ma puoi gestirlo nel software riportando a un altro contatore di risoluzione inferiore se lo desideri), ma puoi farlo quindi scarta parte di quella risoluzione quando fai i tuoi calcoli se non ne hai bisogno in quel momento.

La stima del tempo di esecuzione in un sistema multitasking cooperativo come questo non è mai buona, e spesso non se ne preoccupa per questo. È sempre una stima preliminare con i risultati distorti dalla misurazione, poiché la misurazione è sincrona con l'esecuzione delle attività.

In un vero e proprio sistema multitasking a cambio di contesto normalmente si opererebbe sul numero di "tick" del sistema piuttosto che sul tempo. Cioè, conta semplicemente 1 ogni volta che un'attività viene inserita dal selettore di contesto. Poiché ogni attività ha un runtime fisso controllato dalla granularità dello scheduler, è quindi banale sapere che l'attività X è stata in esecuzione per X/TOTAL_TICKS% del tempo. Eventuali calcoli verranno eseguiti essi stessi in un'attività, ad esempio l'attività inattiva o un'attività di contabilità del sistema.

JRobert Aug 23 2020 at 20:27

Il modo più economico (in tutti i sensi) che conosco per visualizzare "l'ozio" è impostare un pin di output quando si accede allo scheduler e cancellarlo di nuovo quando si invia un'attività (diversa dall'attività inattiva, se ce n'è una). Oppure invertire questa operazione, se invece si desidera visualizzare il carico. Questi possono essere eseguiti con una singola istruzione ciascuno. Basso impatto su memoria, tempo di esecuzione, tempo di assemblaggio e portafoglio.

Con che precisione vuoi segnalarlo?

  • Un LED sull'uscita si affievolirà e si illuminerà in base al carico. Se hai bisogno di smorzamento (forse il carico è breve e "picco-y") aggiungi un filtro RC al circuito LED.

  • Collega un DVM, o meglio ancora, un VOM meccanico se riesci a trovarne uno, all'uscita e leggi il carico dal contatore. Un misuratore meccanico farebbe un po' di smorzamento per te. Un DVM avrà probabilmente bisogno del filtro del suggerimento precedente.

  • Riporta l'uscita filtrata in un ingresso analogico e leggi il carico con una precisione di ~ 0,1%! Questo è molto più significativo di quanto sia giustificato così come lo ritieni opportuno.