Scrivere in C per Performance? [chiuso]


32

So di aver sentito abbastanza spesso che C in genere ha un vantaggio in termini di prestazioni rispetto a C ++. Non ci ho pensato nient'altro fino a quando mi sono reso conto che MSVC non sembra nemmeno supportare il più recente standard di C, ma il più recente lo supporta C99 (per quanto ne so).

Avevo intenzione di scrivere una libreria con del codice da renderizzare in OpenGL in modo da poterlo riutilizzare. Avevo intenzione di scrivere la libreria in C poiché qualsiasi aumento delle prestazioni è il benvenuto quando si tratta di grafica.

Ma ne varrebbe davvero la pena? Il codice che utilizza la libreria verrebbe probabilmente scritto in C ++ e preferisco codificare in C ++ in generale.

Tuttavia, se produrrebbe anche una piccola differenza nelle prestazioni, probabilmente andrei con C.

Si può anche notare che questa libreria sarebbe qualcosa che farei funzionare su Windows / OS X / Linux e probabilmente compilerei tutto nativamente (MSVC per Windows, Clang o GCC per OS X e GCC per Linux .. .o forse i compilatori Intel per tutto).

Mi sono guardato intorno e ho trovato alcuni parametri di riferimento e simili, ma tutto ciò che ho visto ha avuto a che fare con GCC piuttosto che con MSVC e Clang. Inoltre, i parametri di riferimento non menzionano gli standard delle lingue utilizzate. Qualcuno ha qualche idea su questo?

MODIFICARE:Volevo solo condividere il mio punto di vista su questa domanda dopo un paio d'anni di esperienza in più. Ho finito per scrivere il progetto per cui stavo ponendo questa domanda in C ++. Ho iniziato un altro progetto nello stesso periodo in C mentre cercavamo di ottenere qualsiasi piccola prestazione possibile e avevo bisogno che il progetto fosse collegabile in C. Un paio di mesi fa, ho raggiunto il punto in cui avevo davvero bisogno di mappe e avanzato manipolazione di stringhe. Conoscevo le capacità per questo nella libreria standard C ++ e alla fine giunsi alla conclusione che quelle strutture nella libreria standard avrebbero probabilmente sovraperformato e sarebbero state più stabili delle mappe e delle stringhe che avrei potuto implementare in C in un ragionevole lasso di tempo. Il requisito per essere collegabili in C è stato facilmente soddisfatto scrivendo un'interfaccia C nel codice C ++, che è stato fatto rapidamente con tipi opachi. Riscrivere la libreria in C ++ sembrava andare molto più veloce rispetto a quando la scrivevo in C ed era meno soggetta a bug, in particolare a perdite di memoria. Sono stato anche in grado di utilizzare la libreria di threading della libreria standard, che è stata molto più semplice rispetto alle implementazioni specifiche della piattaforma. Alla fine, credo che scrivere la libreria in C ++ abbia portato grandi benefici con un possibile costo di prestazione. Non ho ancora fatto il benchmark della versione C ++, ma credo che possa anche essere possibile che abbia ottenuto alcune prestazioni usando strutture di dati di libreria standard rispetto a quelle che ho scritto. Credo che scrivere la libreria in C ++ abbia portato a grandi benefici con un possibile costo di prestazione ridotto. Non ho ancora fatto il benchmark della versione C ++, ma credo che possa anche essere possibile che abbia ottenuto alcune prestazioni usando strutture di dati di libreria standard rispetto a quelle che ho scritto. Credo che scrivere la libreria in C ++ abbia portato a grandi benefici con un possibile costo di prestazione ridotto. Non ho ancora fatto il benchmark della versione C ++, ma credo che possa anche essere possibile che abbia ottenuto alcune prestazioni usando strutture di dati di libreria standard rispetto a quelle che ho scritto.


9
Il più recente supporto MSVC è in realtà C89.
detly

4
@detly In Visual Studio 2013 è supportata la maggior parte delle funzionalità di C99 . Non è un supporto completo, ma scommetto che in pratica va bene usarlo per scrivere C99.
congusbongus,

4
@ danielu13 - Dove hai sentito esattamente che C ha un vantaggio in termini di prestazioni rispetto a C ++?
Ramhound,

1
@ Sebastian-LaurenţiuPlesciuc Non penso che quei link siano effettivamente utili. Il primo potrebbe essere ben contrastato con quasi la stessa domanda programmers.stackexchange.com/q/113295/76444 ma a favore di c ++ anziché di c come nel tuo link. Per il tuo secondo link, è solo una manciata di linus torvalds. Spero che ormai tutti sappiano che ama davvero odiare il c ++ e non lo toccherebbe con un bastone, ma le sue affermazioni sul c ++ sono quasi oggettive, sono piene di opinioni personali e parzialità e non riflettono davvero la realtà del linguaggio . Almeno questa è la mia opinione .
user1942027

1
@RaphaelMiedl Ho anche detto che questo è stato scritto nel 2007, che è stato molto tempo fa, i compilatori C ++ e il linguaggio C ++ si sono evoluti da allora. Indipendentemente da ciò, spetta al programmatore scegliere quale lingua utilizzare.
Sebastian-Laurenţiu Plesciuc

Risposte:


89

Immagino che le persone affermino spesso che C è più veloce di C ++ perché è più facile ragionare sulle prestazioni in C. C ++ non è intrinsecamente più lento o più veloce, ma alcuni codici C ++ potrebbero oscurare penali nascoste sulle prestazioni. Ad esempio, possono esserci copie e conversioni implicite che non sono immediatamente visibili quando si guarda un pezzo di codice C ++.

Prendiamo la seguente dichiarazione:

foo->doSomething(a + 5, *c);

Supponiamo inoltre che doSomethingabbia la seguente firma:

void doSomething(int a, long b);

Ora proviamo ad analizzare il possibile impatto sulle prestazioni di questa particolare affermazione.

In C, le implicazioni sono abbastanza chiare. foopuò essere solo un puntatore a una struttura e doSomethingdeve essere un puntatore a una funzione. *cdereferenzia una lunga ed a + 5è un'aggiunta intera. L'unica incertezza deriva dal tipo di a: se non è un int, ci sarà una conversione. ma a parte questo, è facile quantificare l'impatto sulle prestazioni di questa singola affermazione.

Passiamo ora al C ++. La stessa affermazione ora può avere caratteristiche prestazionali molto diverse:

  1. doSomethingpotrebbe essere una funzione membro non virtuale (economica), una funzione membro virtuale (un po 'più costosa) std::function, lambda ... ecc. Quel che è peggio, foopotrebbe essere un tipo di classe che si sovraccarica operator->con qualche operazione di complessità sconosciuta. Quindi, al fine di quantificare il costo della chiamata doSomething, è ora necessario conoscere la natura esatta di fooe doSomething.
  2. apotrebbe essere un numero intero o un riferimento a un numero intero (ulteriore riferimento indiretto) o un tipo di classe che implementa operator+(int). L'operatore potrebbe persino restituire un altro tipo di classe a cui è implicitamente convertibile int. Ancora una volta, il costo della prestazione non è evidente dalla sola dichiarazione.
  3. cpotrebbe essere un'implementazione di tipo di classe operator*(). Potrebbe anche essere un riferimento a un long*ecc.

Ottieni l'immagine. A causa delle caratteristiche del linguaggio di C ++, è molto più difficile quantificare i costi di prestazione di una singola istruzione rispetto a quelli di C. Ora, inoltre, astrazioni come std::vector, std::stringsono comunemente usate in C ++, che hanno caratteristiche di prestazione proprie e nascondono allocazioni dinamiche di memoria ( vedi anche la risposta di @ Ian).

Quindi, la linea di fondo è: in generale, non vi è alcuna differenza nelle possibili prestazioni ottenibili utilizzando C o C ++. Ma per un codice veramente critico per le prestazioni, le persone spesso preferiscono usare C perché ci sono molte meno penalità nascoste per le prestazioni.


1
Risposta superba. Questo è ciò a cui alludevo nella mia risposta, ma l'hai spiegato molto meglio.
Ian Goldby,

4
Questa dovrebbe davvero essere la risposta accettata. Spiega perché esistono affermazioni come "C è più veloce di C ++". Il C può essere più veloce o più lento del C ++, ma di solito è molto più facile capire perché uno specifico pezzo di codice C è veloce / lento, il che di solito rende anche più facile l'ottimizzazione.
Leone,

Non prendere nulla da questa risposta eccellente (per cui un +1 da parte mia), ma il compilatore (i) può essere le mele e le arance in questo confronto. Potrebbe generare un codice identico per C vs. C ++, oppure no. Naturalmente lo stesso si può dire per due compilatori o opzioni di compilatore, anche quando si compila lo stesso programma fisicamente con gli stessi presupposti del linguaggio di origine.
JRobert

4
Vorrei aggiungere che la maggior parte dei runtime C ++ sono enormi rispetto al runtime C equivalente, il che importa se si è limitati alla memoria.
James Anderson,

@JamesAnderson Se sei veramente che la memoria limitata, probabilmente non hanno bisogno di un tempo di esecuzione a tutti :)
Navin

30

Il codice scritto in C ++ può essere più veloce che in C, per alcuni tipi di attività.

Se preferisci C ++, usa C ++. Qualsiasi problema di prestazioni sarà insignificante rispetto alle decisioni algoritmiche del tuo software.


6
Può essere più veloce ma potrebbe non essere più veloce per lo stesso motivo.
Rob,

Puoi fornire alcuni esempi di codice ottimizzato scritto in C ++ che è più veloce di C ottimizzato?

1
@ TomDworzanski: un esempio è che usando i template, le decisioni sui percorsi del codice possono essere determinate al momento della compilazione e finiscono con l'hardcoded nel binario finale, piuttosto che con i condizionali e le ramificazioni come sarebbero richieste se fosse scritto in c, così come l'abilità per evitare chiamate di funzione tramite inline.
whatsisname

23

Uno dei principi di progettazione di C ++ è che non paghi per le funzionalità che non usi. Quindi, se scrivi codice in C ++ ed eviti funzionalità che non esistono in C, il codice compilato risultante dovrebbe essere equivalente nelle prestazioni (anche se dovresti misurarlo).

L'utilizzo delle classi comporta costi trascurabili, ad esempio, rispetto alle strutture e ad un gruppo di funzioni associate. Le funzioni virtuali costeranno un po 'di più e dovresti misurare le prestazioni per vedere se è importante per la tua applicazione. Lo stesso vale per qualsiasi altra funzionalità del linguaggio C ++.


3
L'overhead di invio della funzione virtuale è quasi trascurabile, a meno che tu non sia andato IN MODO esagerato nel decomporre e rendere virtuali le cose. Le vtabili saranno di dimensioni ridotte rispetto al resto del codice e dei dati e il ramo indicizzato attraverso la vtable aggiunge alcuni clock a ciascuna chiamata di routine. Dato che le invocazioni di routine, tutte in alto, invocano il ritorno, saranno ovunque da poche centinaia a qualche milione di orologi, il ramo vtable sarà sepolto nel rumore di fondo.
John R. Strohm,

6
Le strutture sono classi in C ++.
destra del

2
@rightfold: Certo, ma puoi ancora scrivere codice C ++ che passa i puntatori alle strutture senza usare la thisfunzione del linguaggio dei puntatori. Questo è tutto ciò che stavo dicendo.
Greg Hewgill

4
@John Il vero costo non è il riferimento indiretto (anche se sono abbastanza sicuro che questo in qualche modo rovinerà anche con alcuni prefetch di processori), ma il fatto che non puoi incorporare funzioni virtuali (almeno in C ++) che non consente molte altre possibilità possibili ottimizzazioni. E sì, ciò può avere un'influenza gigantesca sulle prestazioni.
Voo

2
@Voo Per essere onesti, lo stesso si può dire del codice C equivalente (codice che emula manualmente il polimorfismo di runtime). La differenza più grande è che credo che sarebbe più facile per un compilatore determinare se detta funzione potrebbe essere incorporata in C ++.
Thomas Eding,

14

Uno dei motivi per cui le lingue di livello superiore a volte sono più lente è che possono nascondere dietro le quinte molta più gestione della memoria rispetto alle lingue di livello inferiore.

Qualsiasi linguaggio (o libreria, API, ecc.) Che toglie dettagli di basso livello può potenzialmente nascondere operazioni costose. Ad esempio, in alcune lingue semplicemente tagliando lo spazio bianco finale da una stringa si ottiene un'allocazione di memoria e una copia della stringa. L'allocazione e la copia della memoria, in particolare, possono diventare costose se si verificano ripetutamente in un ciclo stretto.

Se scrivessi questo tipo di codice in C sarebbe palesemente ovvio. In C ++ forse meno, perché le allocazioni e la copia potrebbero essere astratte in una classe da qualche parte. Potrebbero persino essere nascosti dietro un operatore sovraccarico dall'aspetto innocente o un costruttore di copie.

Quindi usa C ++ se vuoi. Ma non lasciarti sedurre dall'apparente convenienza delle astrazioni quando non sai cosa si nasconde sotto di esse.

Naturalmente, usa un profiler per scoprire cosa sta veramente rallentando il tuo codice.


5

Per quello che vale, tendo a scrivere le mie librerie in C ++ 11 per il set di funzionalità avanzate. Mi piace essere in grado di trarre vantaggio da cose come puntatori condivisi, eccezioni, programmazione generica e altre funzionalità solo in C ++. Mi piace C ++ 11 perché ho scoperto che una buona parte di esso è supportata su tutte le piattaforme a cui tengo. Visual Studio 2013 ha molte funzionalità del linguaggio di base e implementazioni di librerie pronte all'uso e presumibilmente sta lavorando per aggiungere il resto. Come ben sapete, anche Clang e GCC supportano l'intero set di funzionalità.

Detto questo, di recente ho letto di una strategia davvero eccezionale riguardo allo sviluppo di librerie che ritengo sia direttamente pertinente alla tua domanda. L'articolo si intitola "Stile di gestione degli errori AC che si adatta bene alle eccezioni C ++" Stefanu Du Toit si riferisce a questa strategia come a un modello a "clessidra". Il primo paragrafo dell'articolo:

Ho scritto molto codice di libreria usando quello che chiamo un modello "a clessidra": implemento una libreria (nel mio caso, in genere, usando C ++), la avvolgo in un'API C che diventa l'unico punto di accesso alla libreria, quindi avvolgi quell'API C in C ++ o in qualche altro linguaggio (s) per fornire una ricca astrazione e una sintassi conveniente. Quando si tratta di codice multipiattaforma nativo, le API C offrono stabilità ABI senza precedenti e portabilità in altre lingue tramite FFI. Limito persino l'API a un sottoinsieme di C che so sia portabile su una vasta gamma di SFI e isola la biblioteca da perdite di modifiche nelle strutture di dati interne - aspettatevi di più su questo nei prossimi post del blog.


Ora per rispondere alla tua preoccupazione principale: le prestazioni.

Suggerirei, come molte altre risposte qui, che scrivere codice in entrambe le lingue funzionerebbe altrettanto bene dal punto di vista delle prestazioni. Da un punto di vista personale, trovo che scrivere il codice corretto in C ++ sia più facile a causa delle caratteristiche del linguaggio, ma penso che sia una preferenza personale. Ad ogni modo, i compilatori sono davvero intelligenti e tendono comunque a scrivere codice migliore di te. Questo vuol dire che il compilatore probabilmente ottimizzerà il tuo codice meglio di quanto potresti.

So che molti programmatori lo dicono, ma la prima cosa che dovresti fare è scrivere il tuo codice, quindi profilarlo e fare ottimizzazioni dove il tuo profiler ti suggerisce di fare. Il tuo tempo sarà molto meglio speso a produrre funzionalità e quindi a ottimizzarlo una volta che puoi vedere dove sono i colli di bottiglia.


Ora, per alcune letture divertenti su come funzioni e ottimizzazioni del linguaggio possano davvero funzionare a tuo favore:

std :: unique_ptr ha zero overhead

constexp consente il calcolo in fase di compilazione

spostare la semantica impedisce oggetti temporanei non necessari


std::unique_ptr has zero overheadQuesto non può essere vero (tecnicamente parlando) perché deve avere il suo costruttore chiamato se lo stack si svolge a causa di un'eccezione. Un puntatore non elaborato non ha questo sovraccarico ed è comunque corretto se il tuo codice probabilmente non genererà. Un compilatore non sarà in grado di dimostrarlo nel caso generale.
Thomas Eding

2
@ThomasEding Mi riferivo alle dimensioni e al sovraccarico di runtime rispetto al codice senza eccezioni. Correggimi se sbaglio, ma ci sono modelli di esecuzione che comportano un sovraccarico di runtime zero quando non vengono generate eccezioni che consentono comunque di propagare le eccezioni quando necessario. Anche così, quando mai un'eccezione potrebbe essere lanciata nel costruttore di unique_ptr? È dichiarato noexcept, e quindi almeno gestisce tutte le eccezioni, ma non riesco a immaginare quale tipo di eccezione possa essere lanciata in primo luogo.
vmrob

vmrob: Perdonatemi ... Volevo scrivere "destructor" invece di "costruttore". Intendevo anche scrivere "non proverà a lanciare". Eeek!
Thomas Eding,

2
@ThomasEding Sai, non penso che importerebbe nemmeno se il distruttore lanciasse un'eccezione. Fintanto che il distruttore non introduce nuove eccezioni, è comunque zero distruzione ambientale. Inoltre, credo che l'intero distruttore venga inserito in una singola chiamata di eliminazione / libera con ottimizzazioni.
vmrob

4

La differenza di prestazioni tra C ++ e C non è dovuta a nulla nella lingua, a rigor di termini, ma in ciò che ti tenta di fare. È come una carta di credito contro contanti. Non ti fa spendere di più, ma lo fai comunque, a meno che tu non sia molto disciplinato.

Ecco un esempio di un programma scritto in C ++, che è stato poi ottimizzato in modo aggressivo per le prestazioni. Devi sapere come eseguire l'ottimizzazione aggressiva delle prestazioni, indipendentemente dalla lingua. Il metodo che utilizzo è una pausa casuale, come mostrato in questo video .

I tipi di cose costose che C ++ ti invita a fare sono gestione eccessiva della memoria, programmazione in stile notifica, affidamento del contatore del programma a librerie di astrazione multistrato (come diceva @Ian), nascondimento della lentezza, ecc.


2

C non ha alcun vantaggio in termini di prestazioni rispetto a C ++ se si fanno le stesse cose in entrambe le lingue. Puoi prendere qualsiasi vecchio codice C scritto da qualsiasi programmatore C decente e trasformarlo in un codice C ++ valido ed equivalente, che verrà eseguito altrettanto velocemente (a meno che tu e il tuo compilatore non sappiate cosa fa la parola chiave "restringi" e lo utilizziate in modo efficace, ma la maggior parte delle persone no).

Il C ++ può avere prestazioni estremamente diverse, sia più lente che più veloci, se (1) usi la libreria C ++ standard per fare cose che possono essere fatte molto più velocemente e più facilmente senza usare la libreria, o (2) se usi la libreria C ++ standard fare le cose molto più facilmente e più velocemente che reimplementando la libreria in cattivi C.


1
questo non sembra offrire nulla di sostanziale rispetto a quanto spiegato in 6 risposte precedenti
moscerino del

Penso che questa risposta menzioni un punto importante che nessun altro ha menzionato. A prima vista sembra che C ++ sia un superset di C, quindi se puoi scrivere un'implementazione C veloce allora dovresti essere in grado di scrivere un'implementazione C ++ equivalente. Tuttavia, C99 supporta la parola chiave restringente che consente di evitare l'aliasing puntatore indesiderato. C ++ non ha tale supporto. La possibilità di evitare l'alias puntatore è una caratteristica importante di Fortran che lo rende utile per applicazioni ad alte prestazioni. Mi aspetto che sia anche possibile ottenere prestazioni migliori dal C99 rispetto al C ++ in domini simili.
user27539
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.