È davvero una buona pratica disabilitare le ottimizzazioni durante le fasi di sviluppo e debug?


15

Ho letto Programmazione di microcontrollori PIC a 16 bit in C , e c'è questa affermazione nel libro:

Durante le fasi di sviluppo e debug di un progetto, tuttavia, è sempre buona norma disabilitare tutte le ottimizzazioni poiché potrebbero modificare la struttura del codice analizzato e rendere problematici il posizionamento singolo e il punto di interruzione.

Confesso di essere stato un po 'confuso. Non capivo se l'autore lo avesse detto a causa del periodo di valutazione C30 o se fosse davvero una buona pratica.

Mi piacerebbe sapere se usi davvero questa pratica e perché?

Risposte:


16

Questo è piuttosto standard nell'ingegneria del software nel suo insieme - quando si ottimizza il codice, al compilatore è consentito riorganizzare le cose praticamente come vuole, a condizione che non si sappia alcuna differenza nel funzionamento. Quindi, ad esempio, se si inizializza una variabile all'interno di ogni iterazione di un ciclo e non si modifica mai la variabile all'interno del ciclo, l'ottimizzatore può spostare tale inizializzazione fuori dal ciclo, in modo da non perdere tempo con esso.

Potrebbe anche rendersi conto che si calcola un numero con il quale quindi non si fa nulla prima di sovrascrivere. In tal caso, potrebbe eliminare il calcolo inutile.

Il problema con l'ottimizzazione è che vorrai mettere un breakpoint su un pezzo di codice, che l'ottimizzatore ha spostato o eliminato. In tal caso, il debugger non può fare ciò che vuoi (in genere, chiuderà il punto di interruzione da qualche parte). Quindi, per rendere il codice generato più simile a quello che hai scritto, disattivi le ottimizzazioni durante il debug - questo assicura che il codice su cui vuoi rompere sia davvero lì.

Devi stare attento con questo, tuttavia, poiché a seconda del tuo codice, l'ottimizzazione può rompere le cose! In generale, il codice che viene rotto da un ottimizzatore che funziona correttamente è in realtà solo un codice errato che riesce a cavarsela con qualcosa, quindi di solito vuoi capire perché l'ottimizzatore lo rompe.


5
Il contrappunto a questo argomento è che l'ottimizzatore probabilmente renderà le cose più piccole e / o più veloci e se hai un codice vincolato in termini di tempistiche o dimensioni, potresti rompere qualcosa disabilitando l'ottimizzazione e perdere tempo a eseguire il debug di un problema che non funziona davvero esiste. Naturalmente, il debugger potrebbe rendere il codice più lento e più grande.
Kevin Vermeer

Dove posso saperne di più su questo?
Daniel Grillo

Non ho lavorato con il compilatore C30 ma per il compilatore C18 c'era una nota / manuale dell'app per il compilatore che copriva le ottimizzazioni supportate.
Mark

@O Engenheiro: controlla i documenti del compilatore per quali ottimizzazioni supporta. L'ottimizzazione varia notevolmente a seconda del compilatore, delle librerie e dell'architettura di destinazione.
Michael Kohne

Ancora una volta, non per il compilatore C30, ma gcc pubblica un elenco LUNGO delle varie ottimizzazioni che possono essere applicate. È inoltre possibile utilizzare questo elenco per ottenere un'ottimizzazione dettagliata, nel caso in cui si abbia una struttura di controllo particolare che si desidera mantenere intatta. L'elenco è qui: gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html
Kevin Vermeer

7

Ho inviato questa domanda a Jack Ganssle e questo è ciò che mi ha risposto:

Daniel,

Preferisco eseguire il debug utilizzando le ottimizzazioni presenti nel codice rilasciato. La NASA dice "prova ciò che voli, vola ciò che test". In altre parole, non eseguire test e quindi modificare il codice!

Tuttavia, a volte è necessario disattivare le ottimizzazioni in modo che il debugger funzioni. Provo a spegnerli solo nei moduli su cui sto lavorando. Per questo motivo credo nel mantenere piccoli file, diciamo qualche centinaio di righe di codice o giù di lì.

Ti auguro il meglio, Jack


Penso che ci sia una distinzione non dichiarata tra i due paragrafi in questa risposta. Il test si riferisce a una procedura che dovrebbe dimostrare che le merci morbide, ferme e / o difficili funzionano correttamente. Il debug è il processo in cui il codice viene esaminato istruzioni per istruzioni per capire perché non funziona [ancora].
Kevin Vermeer,

È bello avere una scelta. Quindi i test possono coprire più varietà / permutazioni con e senza ottimizzazione. Maggiore è la copertura, meglio è il test

@reemrevnivek, quando esegui il debug, non stai testando anche tu?
Daniel Grillo,

@O Engenheiro - No. Ho eseguito il debug solo se il test ha esito negativo.
Kevin Vermeer,

6

Dipende, e questo è generalmente vero per tutti gli strumenti, non solo per C30.

Le ottimizzazioni spesso rimuovono e / o ristrutturano il codice in vari modi. L'istruzione switch può essere reimplementata con un costrutto if / else o in alcuni casi può essere rimossa tutta insieme. y = x * 16 può essere sostituito con una serie di turni a sinistra, ecc. sebbene quest'ultimo tipo di ottimizzazione possa di solito essere ancora superato, è soprattutto la ristrutturazione dell'istruzione di controllo che ti ottiene.

Ciò può rendere impossibile il passaggio di un debugger nel codice C poiché le strutture definite in C non esistono più, sono state sostituite o riordinate dal compilatore in qualcosa che il compilatore ritiene sia più veloce o utilizza meno spazio. Può anche rendere impossibile impostare i punti di interruzione dall'elenco C poiché le istruzioni su cui si è interrotti potrebbero non esistere più. Ad esempio, potresti provare a impostare un punto di interruzione all'interno di un'istruzione if, ma il compilatore potrebbe averlo rimosso. Puoi provare a impostare un punto di interruzione all'interno di un ciclo while o for, ma il compilatore ha deciso di srotolarlo in modo che non esista più.

Per questo motivo, se è possibile eseguire il debug con ottimizzazioni disattivate, in genere è più semplice. Devi sempre ripetere il test con le ottimizzazioni attivate. Questo è l'unico modo per scoprire che ti sei perso un importantevolatile e che causa guasti intermittenti (o qualche altra stranezza).

Nel caso dello sviluppo integrato, devi comunque fare attenzione alle ottimizzazioni. Soprattutto nelle sezioni di codice che sono fondamentali per il tempismo, ad esempio alcuni interrupt. In questi casi è necessario codificare i bit critici nell'assembly o utilizzare le direttive del compilatore per assicurarsi che queste sezioni non siano ottimizzate, in modo da sapere che hanno un tempo di esecuzione fisso o un tempo di esecuzione nel caso peggiore fisso.

L'altro gotcha può inserire il codice nell'UC, potrebbe essere necessario ottimizzare la densità del codice per adattare semplicemente il codice al chip. Questo è uno dei motivi per cui di solito è una buona idea iniziare con la più grande capacità della ROM in una famiglia e sceglierne una più piccola per la produzione, dopo che il codice è bloccato.


5

In genere, eseguivo il debug con qualsiasi impostazione che avevo pianificato di rilasciare. Se avessi rilasciato un codice ottimizzato, avrei eseguito il debug con un codice ottimizzato. Se avessi rilasciato un codice non ottimizzato, avrei eseguito il debug con codice non ottimizzato. Lo faccio per due motivi. Innanzitutto, gli ottimizzatori possono fare differenze di tempistica abbastanza significative da comportare un comportamento del prodotto finale diverso dal codice non ottimizzato. In secondo luogo, anche se la maggior parte è abbastanza buona, i produttori di compilatori commettono errori e il codice ottimizzato può produrre risultati diversi dal codice non ottimizzato. Di conseguenza, mi piace ottenere il maggior tempo di test possibile con qualsiasi impostazione con cui prevedo di rilasciare.

Detto questo, gli ottimizzatori possono rendere difficile il debug, come indicato nelle risposte precedenti. Se trovo una particolare sezione di codice difficile da eseguire il debug, disattivo temporaneamente l'ottimizzatore, eseguo il debug per far funzionare il codice, quindi riattivo l'ottimizzatore e testiamo ancora una volta.


1
Ma il solo passaggio del codice può essere quasi impossibile con le ottimizzazioni attivate. Eseguire il debug con ottimizzazioni disattivate ed eseguire i test delle unità con il codice di rilascio.
Rocketmagnet,

3

La mia normale strategia è quella di sviluppare con l'ottimizzazione finale (massimo per dimensioni o velocità secondo necessità), ma disattivare temporaneamente l'ottimizzazione se devo eseguire il debug o rintracciare qualcosa. Ciò riduce il rischio di affioramento di bug a seguito della modifica dei livelli di ottimizzazione.

Una tipica modalità di errore è quando l'aumento dell'ottimizzazione causa la comparsa di bug mai visti prima perché non hai dichiarato variabili volatili dove necessario - questo è essenziale per dire al compilatore quali cose non dovrebbero essere "ottimizzate".


2

Utilizzare qualsiasi modulo con cui si intende rilasciare, i debugger e la compilazione per il debug nascondono molto (MOLTO) bug che non si vedono fino a quando non si compila per il rilascio. A quel punto è molto più difficile trovare quei bug, rispetto al debug mentre procedi. 20 anni fa e non ho mai avuto un uso per un debugger gdb o altro, non c'è bisogno di guardare le variabili o un singolo passaggio. Da centinaia a migliaia di righe al giorno. Quindi è possibile, non essere portato a pensare diversamente.

La compilazione per il debug, quindi la compilazione successiva per il rilascio può e richiederà il doppio o il doppio dello sforzo. Se si entra in un bind e si deve usare uno strumento come un debugger, compilare il debugger per risolvere il problema specifico, quindi tornare al normale funzionamento.

Altri problemi sono anche veri come l'ottimizzatore rende il codice più veloce, quindi per incorporare in particolare i cambiamenti di temporizzazione con le opzioni del compilatore e che possono influenzare la funzionalità del programma, qui utilizza nuovamente la scelta di compilazione consegnabile durante l'intera fase. Anche i compilatori sono programmi e hanno errori e ottimizzatori che commettono errori e alcuni non hanno alcuna fiducia in questo. Se questo è il caso, non c'è niente di sbagliato nel compilare senza ottimizzazione, fallo sempre in questo modo. Il percorso che preferisco è compilare per l'ottimizzazione, quindi se sospetto che un problema del compilatore disabiliti l'ottimizzazione, se ciò lo risolve di solito va avanti e indietro a volte esaminando l'output dell'assemblatore per capire perché.


1
+1 solo per elaborare la tua buona risposta: spesso la compilazione in modalità "debug" riempie lo stack / heap attorno a variabili con spazio non allocato per mitigare piccoli errori di scrittura e formattazione delle stringhe. Più spesso otterrai un buon arresto anomalo del runtime se lo compili in versione.
Morten Jensen,

2

Sviluppo sempre il codice con -O0 (opzione gcc per disattivare l'ottimizzazione). Quando sento di essere nel punto in cui voglio iniziare a lasciare che le cose si dirigano maggiormente verso una versione, inizierò con -Os (ottimizza per dimensioni) poiché in genere più codice puoi conservare nella cache, meglio sarà, anche se non è ottimizzato per il super-duper.

Trovo che gdb funzioni molto meglio con il codice -O0, ed è molto più facile da seguire se devi entrare nell'assembly. Commutando tra -O0 e -Os puoi anche vedere cosa sta facendo il compilatore al tuo codice. A volte è un'educazione piuttosto interessante e può anche scoprire bug del compilatore ... quelle cose brutte che ti fanno strappare i capelli cercando di capire cosa c'è che non va nel tuo codice!

Se ne ho davvero bisogno, inizierò ad aggiungere in -fdata-sezioni e -fcode-sezioni con --gc-sezioni, che consentono al linker di rimuovere intere funzioni e segmenti di dati che non sono effettivamente utilizzati. Ci sono molte piccole cose con cui puoi armeggiare per cercare di ridurre ulteriormente le cose o renderle più veloci, ma nel complesso questi sono gli unici trucchi che finisco per usare, e tutto ciò che deve essere più piccolo o più veloce lo porterò -Montare.


2

Sì, la disabilitazione delle ottimizzazioni durante il debug è la migliore pratica da un po 'di tempo, per tre motivi:

  • (a) se hai intenzione di eseguire il singolo passaggio del programma con un debugger di alto livello, è leggermente meno confuso.
  • (a) (obsoleto) se hai intenzione di eseguire il debug in un'unica fase del programma con un debugger in linguaggio assembly, è molto meno confuso. (Ma perché dovresti preoccuparti di questo quando potresti usare un debugger di alto livello?)
  • (b) (lungo obsoleto) probabilmente eseguirai questo particolare eseguibile solo una volta, quindi esegui alcune modifiche e ricompila. È una perdita di tempo di una persona aspettare altri 10 minuti mentre il compilatore "ottimizza" questo particolare eseguibile, quando risparmierà meno di 10 minuti di runtime. (Questo non è più rilevante con i PC moderni in grado di compilare un tipico eseguibile microcontrollore, con ottimizzazione completa, in meno di 2 secondi).

Molte persone vanno ancora oltre in questa direzione e spediscono con affermazioni attivate .


Il passaggio singolo attraverso il codice assembly può essere molto utile per diagnosticare casi in cui il codice sorgente in realtà specifica qualcosa di diverso da quello che sembra (ad esempio "longvar1 & = ~ 0x40000000; longvar2 & = ~ 0x80000000;") o in cui un compilatore genera codice errato . Ho rintracciato alcuni problemi usando i debugger di codice macchina che non penso davvero che avrei potuto rintracciare in nessun altro modo.
supercat

2

Semplice: le ottimizzazioni richiedono molto tempo e potrebbero essere inutili se si deve cambiare quel pezzo di codice in un secondo momento durante lo sviluppo. Quindi potrebbero essere una perdita di tempo e denaro.
Sono utili per i moduli finiti, tuttavia; parti del codice che molto probabilmente non avranno più bisogno di modifiche.


2

ha certamente senso nel caso di punti di interruzione ... poiché il compilatore può rimuovere molte istruzioni che non influiscono sulla memoria.

considera qualcosa come:

int i =0;

for (int j=0; j < 10; j++)
{
 i+=j;
}
return 0;

potrebbe essere completamente ottimizzato (perché inon viene mai letto). dal punto di vista del tuo punto di interruzione sembrerebbe che abbia saltato tutto quel codice, quando praticamente non era nemmeno lì .... Presumo che sia il motivo per cui nelle funzioni di tipo sleep vedrai spesso qualcosa del tipo:

for (int j=delay; j != 0; j--)
{
    asm( " nop " );
    asm( " nop " );
}
return 0;

1

Se si utilizza il debugger, disabiliterei le ottimizzazioni e il debug.

Personalmente trovo che il debugger PIC causi più problemi di quanti mi aiuti a risolvere.
Uso semplicemente printf () su USART per eseguire il debug dei miei programmi scritti in C18.


1

La maggior parte degli argomenti contro l'attivazione dell'ottimizzazione nella compilazione si riduce a:

  1. problemi con il debug (connettività JTAG, punti di interruzione ecc.)
  2. tempismo del software errato
  3. sh * t smette di funzionare correttamente

IMHO i primi due sono legittimi, il terzo non tanto. Spesso significa che hai un codice errato o fai affidamento su uno sfruttamento non sicuro del linguaggio / dell'implementazione o forse l'autore è solo un fan del buon vecchio zio comportamento indefinito.

Il blog Embedded in Academia ha una cosa o due da dire sul comportamento indefinito, e questo post parla di come i compilatori lo sfruttano: http://blog.regehr.org/archives/761


Un'altra possibile ragione è che il compilatore potrebbe essere fastidiosamente lento quando le ottimizzazioni sono attive.
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.