2009-03-23 4 views
46

Sono nuovo ai cast di stile C++ e sono preoccupato che l'uso di cast di stile C++ rovinerà le prestazioni della mia applicazione perché ho un real-time-critical deadline nella mia routine di servizio di interrupt.Performance colpita da stili in stile C++?

Ho sentito dire che alcuni calchi generano eccezioni!

Vorrei utilizzare i cast di stile C++ perché renderebbe il mio codice più "robusto". Tuttavia, in caso di risultati delle prestazioni, probabilmente non utilizzerò i cast di stile C++ e passerò invece più tempo a testare il codice che utilizza i cast in stile C.


qualcuno ha fatto alcun test rigorosi/profilatura per confrontare le prestazioni di C++ stile getta allo stile C getta?

Quali sono stati i tuoi risultati?

Quali conclusioni hai tratto?

+0

Potrei dover aggiungere una taglia a questo ... è così che sono interessato. –

+0

Hai già avuto due risposte precise (se lo dico io stesso). –

+0

Le risposte correnti indicano che lo stile statico C++ non ha un impatto sulle prestazioni in fase di esecuzione. Qualcuno potrebbe commentare o aggiungere qualche spiegazione sul perché avrebbero la stessa performance? (La domanda è volutamente vaga.) –

risposta

77

Se il cast in stile C++ può essere sostituito da un cast in stile C, non ci sarà un sovraccarico. Se non può, come nel caso di dynamic_cast, per il quale non esiste un equivalente C, devi pagare il costo in un modo o nell'altro.

Ad esempio, il codice seguente:

int x; 
float f = 123.456; 

x = (int) f; 
x = static_cast<int>(f); 

genera codice identica per entrambi getta con VC++ - codice è:

00401041 fld   dword ptr [ebp-8] 
00401044 call  __ftol (0040110c) 
00401049 mov   dword ptr [ebp-4],eax 

L'unica C++ cast che può gettare è dynamic_cast quando colata a un riferimento. Per evitare questo, lanciare su un puntatore, che restituirà 0 se il cast fallisce.

+3

Un cast in stile C++ non viene mai sostituito da un cast in stile C. Se mai, è il contrario. Lo standard specifica solo il comportamento dei cast di stile C++. Quelli in stile C sono descritti in termini di quelli C++. – jalf

+1

@jalf sì - intendevo sostituito concettualey –

+0

Sì, ma potrebbe valere la pena renderlo 110% chiaro, se l'OP lo accetterà. ;) – jalf

39

L'unico con costi aggiuntivi in ​​fase di esecuzione è dynamic_cast, che ha funzionalità che non possono essere riprodotte direttamente con un cast di stile C comunque. Quindi non hai problemi.

Il modo più semplice per rassicurare ciò è istruire il compilatore a generare l'output dell'assembler ed esaminare il codice generato. Ad esempio, in qualsiasi compilatore sanamente implementato, lo reinterpret_cast scomparirà del tutto, perché significa semplicemente "andare avanti ciecamente e far finta che i dati siano di questo tipo".

4

Quando si utilizza dynamic_cast diversi controlli sono fatti durante il runtime per evitare di fare qualcosa di stupido (di più al GCC mailing list), il costo di una dynamic_cast dipende da quante classi sono interessati, quali classi sono interessati, ecc
Se sei sicuro che il cast sia sicuro, puoi comunque utilizzare reinterpret_cast.

+0

Se trovi che devi usare reinterpret_cast, ti stai sicuramente avventurando in territorio di "implementazione specifica", e possibilmente anche in "comportamento non definito" –

+0

Tieni presente che reinterpret_cast potrebbe essere soggetto a diverse regole di aliasing rispetto a static_cast ecc. – leander

16

Perché ci dovrebbe essere un successo nelle prestazioni? Eseguono esattamente la stessa funzionalità di C cast. L'unica differenza è che registrano più errori in fase di compilazione e sono più facili da cercare nel codice sorgente.

static_cast<float>(3) equivale esattamente a (float)3 e genera esattamente lo stesso codice.

dato un float f = 42.0f reinterpret_cast<int*>(&f) è esattamente equivalente a (int*)&f, e genererà esattamente lo stesso codice.

E così via. L'unico cast che differisce è dynamic_cast, che, sì, può generare un'eccezione. Ma è perché fa cose che il cast in stile C non può fare. Quindi non utilizzare dynamic_cast a meno che non sia necessaria la sua funzionalità.

In genere è sicuro assumere che gli scrittori di compilatori siano intelligenti. Date due espressioni diverse che hanno la stessa semantica secondo lo standard, di solito è sicuro assumere che saranno implementate in modo identico nel compilatore.

Oops: il secondo esempio deve essere reinterpret_cast, non dynamic_cast, ovviamente. Risolto adesso

Ok, giusto per rendere assolutamente chiaro, qui è quello che dice il ++ standard C:

§5.4.5:

Le conversioni eseguite da

  • un const_cast (5.2 .11)
  • a static_cast (5.2,9)
  • a static_cast, seguita da una const_cast
  • un reinterpret_cast (5.2.10), o
  • un reinterpret_cast seguito da un const_cast.

può essere eseguita utilizzando la notazione getto di conversione di tipo esplicita. Si applicano le stesse restrizioni semantiche e i comportamenti . Se una conversione può essere interpretata in più di uno dei modi elencati sopra, l'interpretazione viene visualizzata per la prima volta nell'elenco , anche se il cast risultante da è mal interpretato.

Quindi, se nulla, dal momento che il cast di tipo C è implementata in termini di C++ getta, calchi in stile C dovrebbe essere più lento . (ovviamente non lo sono, perché il compilatore genera lo stesso codice in ogni caso, ma è più plausibile che lo stile del C++ sia più lento.)

+0

Dato il tuo commento di : "di solito è sicuro assumere che verranno implementati in modo identico nel compilatore". Hai o hai svolto lavori incorporati o lavori critici in tempo reale? –

+0

dynamic_cast verrà generato solo quando si opera sui riferimenti. Se sta lavorando su un puntatore, restituirà 0 se il cast fallisce. E perché mai useresti dynamic_cast su una conversione float * in int *? Non ha senso. –

+0

oh oops, intendevo reinterpret_cast, ovviamente. È riparato ora. Grazie per segnalarlo. :) – jalf

3

Anche se sono d'accordo con la frase "l'unico con qualsiasi extra costo in fase di esecuzione è dynamic_cast ", tieni presente che potrebbero esserci differenze specifiche del compilatore.

Ho visto alcuni bug archiviati sul mio compilatore corrente in cui la generazione o l'ottimizzazione del codice era leggermente diversa a seconda che si utilizzasse un cast di tipo static_cast in stile C vs. C++.

Quindi, se sei preoccupato, controlla lo smontaggio degli hotspot. Altrimenti, basta evitare i lanci dinamici quando non ne hai bisogno. (Se si disattiva RTTI, non è comunque possibile utilizzare dynamic_cast.Stile)

+0

Sarei interessato a vedere un esempio dell'uso di dynamic_cast quando non ne hai bisogno. –

+1

Un esempio potrebbe essere se downcast ad una classe derivata (quindi useresti spesso dynamic_cast), ma sai già che il cast è legale, quindi puoi usare static_cast invece. – jalf

+1

@Neil: Credo che intendevo più sulla falsariga di "refactoring per evitare dynamic_cast". Sembro piuttosto duro per le conseguenze prima di iniziare ad attaccare "virtuale" nelle gerarchie di ereditarietà, perché * so * sto facendo un compromesso, e a volte scegliere un modello di design diverso può risolvere il problema ... – leander

15

Ci sono quattro C++ getta:

  • const_cast
  • static_cast
  • reinterpret_cast
  • dynamic_cast

Come già accennato, le prime tre sono le operazioni di compilazione. Non vi è alcuna penalità in fase di esecuzione per il loro utilizzo. Sono messaggi al compilatore che i dati che sono stati dichiarati in un modo devono essere accessibili in un modo diverso. "Ho detto che era uno int*, ma permettetemi di accedervi come se fosse un char* che punta a sizeof(int) char s" o "Ho detto che questi dati erano di sola lettura, e ora ho bisogno di passarlo a una funzione che non modificherà ma non considera il parametro come riferimento const. "

A parte il danneggiamento dei dati mediante il cast del tipo sbagliato e il superamento dei dati (sempre una possibilità con i cast in stile C), il problema più comune in fase di esecuzione di questi cast è dato che i dati effettivamente dichiarati const potrebbero non essere trasferibili a non-const. La trasmissione di qualcosa ha dichiarato const in non-const e quindi la modifica non è definita. Undefined means you're not even guaranteed to get a crash.

dynamic_cast è un costrutto di runtime e deve avere un costo di runtime.

Il valore di questi cast è che specificano in modo specifico quello che stai cercando di trasmettere da/a, attenersi visivamente e possono essere cercati con strumenti cerebrali. Consiglierei di usarli per l'utilizzo di cast in stile C.

+2

In realtà, 'static_cast' può anche comportare un'operazione di runtime, proprio come può fare il cast di stile c (ad es. Quando si esegue il cast da virgola mobile a intero e viceversa). – MikeMB

+1

Perché usare 'static_cast' per convertire tra virgola mobile e interi? Puoi semplicemente assegnare: 'double d = 5; int i = d; '. La conversione può includere un costo di runtime, ma devi pagare quel costo anche se non scrivi 'static_cast'. –

+1

Esistono contesti in cui la conversione implicita del restringimento non è consentita (ad es.lista di inizializzazione) o in cui si desidera rendere esplicita la conversione. Per esempio. per selezionare un certo sovraccarico o per documentare (a te o al compilatore) che sei a conoscenza della conversione in corso. Inoltre, la conversione float su int è solo un esempio: si pensi agli operatori di conversione esplicita/ai costruttori di classi o al passaggio verso l'alto/verso il basso di un puntatore in una gerarchia, con ereditarietà multipla. – MikeMB

1

La verità canonica è l'assemblaggio, quindi provare entrambi e vedere se si ottiene una logica diversa.

Se si ottiene lo stesso identico assemblaggio, non vi è alcuna differenza, non può esserci. L'unico posto in cui devi davvero attenersi ai vecchi cast di C è in pura routine e librerie C, dove non ha senso introdurre la dipendenza da C++ solo per il casting di tipo.

Una cosa di cui essere a conoscenza è che le trasmissioni avvengono ovunque in un pezzo di codice di dimensioni adeguate. In tutta la mia carriera non ho mai cercato "tutte le trasmissioni" in un pezzo di logica: tendi a cercare i cast in un TIPO specifico come "A", e una ricerca su "(A)" è solitamente altrettanto efficiente quanto qualcosa come "static_cast <A>". Usa i cast più recenti per cose come la convalida del tipo e simili, non perché facciano ricerche che non farai mai più facilmente.