Prestazioni C ++ rispetto a Java / C #


119

La mia comprensione è che C / C ++ produce codice nativo da eseguire su una particolare architettura della macchina. Al contrario, linguaggi come Java e C # vengono eseguiti su una macchina virtuale che astrae l'architettura nativa. Logicamente sembrerebbe impossibile per Java o C # eguagliare la velocità di C ++ a causa di questo passaggio intermedio, tuttavia mi è stato detto che gli ultimi compilatori ("hot spot") possono raggiungere questa velocità o addirittura superarla.

Forse questa è più una domanda del compilatore che una domanda sulla lingua, ma qualcuno può spiegare in un inglese semplice come è possibile che uno di questi linguaggi della macchina virtuale funzioni meglio di una lingua madre?


Java e C # possono eseguire l'ottimizzazione in base a come l'applicazione viene effettivamente eseguita utilizzando il codice disponibile in fase di esecuzione. ad esempio, può inserire codice in una libreria condivisa che può effettivamente cambiare mentre il programma è in esecuzione ed essere comunque corretto.
Peter Lawrey

Alcune misurazioni effettive da verificare prima di leggere un sacco di teoria molto instabile in queste risposte: shootout.alioth.debian.org/u32/…
Justicle

Risposte:


178

In generale, C # e Java possono essere altrettanto veloci o più veloci perché il compilatore JIT, un compilatore che compila il tuo IL la prima volta che viene eseguito, può apportare ottimizzazioni che un programma compilato C ++ non può perché può interrogare la macchina. Può determinare se la macchina è Intel o AMD; Pentium 4, Core Solo o Core Duo; o se supporta SSE4, ecc.

Un programma C ++ deve essere compilato in anticipo di solito con ottimizzazioni miste in modo che funzioni decentemente bene su tutte le macchine, ma non è ottimizzato quanto potrebbe essere per una singola configurazione (cioè processore, set di istruzioni, altro hardware).

Inoltre, alcune funzionalità del linguaggio consentono al compilatore in C # e Java di fare ipotesi sul codice che gli consentono di ottimizzare alcune parti che non sono sicure per il compilatore C / C ++. Quando hai accesso ai puntatori ci sono molte ottimizzazioni che non sono sicure.

Anche Java e C # possono eseguire allocazioni di heap in modo più efficiente rispetto a C ++ perché il livello di astrazione tra il garbage collector e il codice gli consente di eseguire tutta la compressione dell'heap contemporaneamente (un'operazione abbastanza costosa).

Ora non posso parlare per Java su questo punto successivo, ma so che C #, ad esempio, rimuoverà effettivamente metodi e chiamate di metodo quando sa che il corpo del metodo è vuoto. E utilizzerà questo tipo di logica in tutto il codice.

Quindi, come puoi vedere, ci sono molti motivi per cui alcune implementazioni C # o Java saranno più veloci.

Detto questo, possono essere apportate ottimizzazioni specifiche in C ++ che spazzeranno via tutto ciò che potresti fare con C #, specialmente nel regno della grafica e ogni volta che sei vicino all'hardware. I puntatori fanno miracoli qui.

Quindi, a seconda di cosa stai scrivendo, preferisco l'uno o l'altro. Ma se stai scrivendo qualcosa che non dipende dall'hardware (driver, videogioco, ecc.), Non mi preoccuperei delle prestazioni di C # (di nuovo non posso parlare di Java). Andrà benissimo.

Dal lato Java, @Swati sottolinea un buon articolo:

https://www.ibm.com/developerworks/library/j-jtp09275


Il tuo ragionamento è falso: i programmi C ++ vengono creati per la loro architettura di destinazione, non hanno bisogno di cambiare in fase di esecuzione.
Justicle

3
@Justicle Il meglio che il tuo compilatore c ++ offrirà per diverse architetture è solitamente x86, x64, ARM e quant'altro. Ora puoi dirgli di utilizzare funzionalità specifiche (ad esempio SSE2) e, se sei fortunato, genererà anche un codice di backup se tale funzionalità non è disponibile, ma è quanto di più dettagliato possibile. Certamente nessuna specializzazione a seconda delle dimensioni della cache e quant'altro.
Voo

4
Vedi shootout.alioth.debian.org/u32/… per esempi di questa teoria che non sta accadendo.
Justicle

1
Ad essere onesti, questa è una delle peggiori risposte. È così infondato, potrei semplicemente invertirlo. Troppa generalizzazione, troppa inconsapevolezza (ottimizzare le funzioni vuote è davvero solo la punta dell'iceberg). Un lussuoso compilatore C ++ ha: Time. Un altro lusso: non viene applicato alcun controllo. Ma trova di più in stackoverflow.com/questions/145110/c-performance-vs-java-c/… .
Sebastian Mach

1
@OrionAdrian ok ora siamo al punto di partenza ... Vedi shootout.alioth.debian.org/u32/… per esempi di questa teoria che non sta accadendo. In altre parole, mostraci che la tua teoria può essere dimostrata corretta prima di fare vaghe affermazioni speculative.
Justicle

197

JIT vs compilatore statico

Come già detto nei post precedenti, JIT può compilare IL / bytecode in codice nativo in fase di runtime. Il costo è stato menzionato, ma non alla sua conclusione:

JIT ha un grosso problema è che non può compilare tutto: la compilazione JIT richiede tempo, quindi JIT compilerà solo alcune parti del codice, mentre un compilatore statico produrrà un binario nativo completo: per alcuni tipi di programmi, lo statico il compilatore supererà facilmente il JIT.

Ovviamente, C # (o Java, o VB) è solitamente più veloce nel produrre una soluzione valida e robusta rispetto al C ++ (se non altro perché C ++ ha una semantica complessa e la libreria standard C ++, sebbene interessante e potente, è piuttosto scarsa se confrontata con la versione completa ambito della libreria standard da .NET o Java), quindi di solito la differenza tra C ++ e .NET o Java JIT non sarà visibile alla maggior parte degli utenti e per quei binari che sono critici, beh, puoi comunque chiamare l'elaborazione C ++ da C # o Java (anche se questo tipo di chiamate native può essere abbastanza costoso di per sé) ...

Metaprogrammazione C ++

Notare che di solito si confronta il codice runtime C ++ con il suo equivalente in C # o Java. Ma C ++ ha una caratteristica che può superare Java / C # fuori dagli schemi, ovvero la metaprogrammazione del modello: l'elaborazione del codice verrà eseguita al momento della compilazione (quindi, aumentando notevolmente il tempo di compilazione), risultando in zero (o quasi zero) runtime.

Ho ancora visto un effetto della vita reale su questo (ho giocato solo con i concetti, ma a quel punto la differenza era di secondi di esecuzione per JIT e zero per C ++), ma vale la pena menzionarlo, insieme al fatto che la metaprogrammazione del modello non lo è banale...

Modifica 2011-06-10: In C ++, giocare con i tipi viene eseguito in fase di compilazione, il che significa produrre codice generico che chiama codice non generico (ad esempio un parser generico dalla stringa al tipo T, chiamando l'API della libreria standard per i tipi T che riconosce, e rendere il parser facilmente estensibile dal suo utente) è molto semplice e molto efficiente, mentre l'equivalente in Java o C # è doloroso nella migliore delle ipotesi da scrivere, e sarà sempre più lento e risolto in fase di esecuzione anche quando i tipi sono noti in fase di compilazione, il che significa che la tua unica speranza è che il JIT inline l'intera cosa.

...

Modifica 2011-09-20: Il team dietro Blitz ++ ( Homepage , Wikipedia ) è andato in questo modo e, a quanto pare, il loro obiettivo è raggiungere le prestazioni di FORTRAN sui calcoli scientifici spostandosi il più possibile dall'esecuzione in runtime al tempo di compilazione, tramite la metaprogrammazione del modello C ++ . Così il " Ho eppure così vedere un vero e proprio effetto vita su questa " parte ho scritto sopra a quanto pare fa esistere nella vita reale.

Utilizzo della memoria C ++ nativa

C ++ ha un utilizzo della memoria diverso da Java / C # e, quindi, presenta vantaggi / difetti diversi.

Indipendentemente dall'ottimizzazione JIT, nulla andrà veloce come l'accesso diretto del puntatore alla memoria (ignoriamo per un momento le cache del processore, ecc.). Quindi, se hai dati contigui in memoria, accedervi tramite puntatori C ++ (cioè puntatori C ... Diamo a Caesar ciò che gli è dovuto) andrà molto più velocemente che in Java / C #. E C ++ ha RAII, che rende molte elaborazioni molto più semplici rispetto a C # o persino a Java. C ++ non ha bisogno usingdi definire l'ambito dell'esistenza dei suoi oggetti. E C ++ non ha una finallyclausola. Questo non è un errore.

:-)

E nonostante le strutture di tipo primitivo C #, gli oggetti C ++ "in pila" non costeranno nulla in fase di allocazione e distruzione e non avranno bisogno di GC per lavorare in un thread indipendente per eseguire la pulizia.

Per quanto riguarda la frammentazione della memoria, gli allocatori di memoria nel 2008 non sono i vecchi allocatori di memoria del 1980 che di solito vengono confrontati con un GC: l'allocazione C ++ non può essere spostata in memoria, vero, ma poi, come su un filesystem Linux: chi ha bisogno del disco rigido deframmentazione quando la frammentazione non avviene? L'utilizzo dell'allocatore giusto per l'attività giusta dovrebbe far parte del toolkit per sviluppatori C ++. Ora, scrivere gli allocatori non è facile, quindi la maggior parte di noi ha cose migliori da fare e, per la maggior parte dell'uso, RAII o GC è più che sufficiente.

Modifica 2011-10-04: per esempi su allocatori efficienti: sulle piattaforme Windows, a partire da Vista, l' Heap a bassa frammentazione è abilitato per impostazione predefinita. Per le versioni precedenti, LFH può essere attivato chiamando la funzione WinAPI HeapSetInformation ). Su altri sistemi operativi, vengono forniti allocatori alternativi (vederehttps://secure.wikimedia.org/wikipedia/en/wiki/Malloc per un elenco)

Ora, il modello di memoria sta diventando un po 'più complicato con l'avvento della tecnologia multicore e multithreading. In questo campo, immagino che .NET abbia il vantaggio e Java, mi è stato detto, ha tenuto il sopravvento. È facile per alcuni hacker "on the bare metal" lodare il suo codice "near the machine". Ma ora, è abbastanza più difficile produrre un assembly migliore a mano che lasciare che il compilatore faccia il suo lavoro. Per C ++, il compilatore è diventato solitamente migliore dell'hacker da un decennio. Per C # e Java, questo è ancora più semplice.

Tuttavia, il nuovo standard C ++ 0x imporrà un semplice modello di memoria ai compilatori C ++, che standardizzerà (e quindi semplificherà) il codice multiprocessing / parallelo / threading efficace in C ++ e renderà le ottimizzazioni più facili e sicure per i compilatori. Ma poi, vedremo tra un paio d'anni se le sue promesse saranno mantenute.

C ++ / CLI contro C # / VB.NET

Nota: in questa sezione, sto parlando di C ++ / CLI, ovvero il C ++ ospitato da .NET, non il C ++ nativo.

La scorsa settimana, ho seguito una formazione sull'ottimizzazione .NET e ho scoperto che il compilatore statico è comunque molto importante. Importante quanto JIT.

Lo stesso codice compilato in C ++ / CLI (o il suo predecessore, Managed C ++) potrebbe essere volte più veloce dello stesso codice prodotto in C # (o VB.NET, il cui compilatore produce lo stesso IL di C #).

Perché il compilatore statico C ++ era molto meglio per produrre codice già ottimizzato rispetto a C #.

Ad esempio, l'inlining di funzioni in .NET è limitato alle funzioni il cui bytecode è inferiore o uguale a 32 byte di lunghezza. Quindi, un po 'di codice in C # produrrà una funzione di accesso da 40 byte, che non sarà mai inline da JIT. Lo stesso codice in C ++ / CLI produrrà una funzione di accesso di 20 byte, che sarà inline da JIT.

Un altro esempio sono le variabili temporanee, che vengono semplicemente compilate dal compilatore C ++ pur essendo ancora menzionate nell'IL prodotto dal compilatore C #. L'ottimizzazione della compilazione statica C ++ risulterà in meno codice, quindi autorizza di nuovo un'ottimizzazione JIT più aggressiva.

La ragione di ciò è stata ipotizzata per essere il fatto che il compilatore C ++ / CLI ha tratto vantaggio dalle vaste tecniche di ottimizzazione del compilatore nativo C ++.

Conclusione

Amo il C ++.

Ma per quanto ne so, C # o Java sono tutto sommato una scommessa migliore. Non perché siano più veloci del C ++, ma perché quando si sommano le loro qualità, finiscono per essere più produttivi, necessitano di meno formazione e hanno librerie standard più complete del C ++. E come per la maggior parte dei programmi, le loro differenze di velocità (in un modo o nell'altro) saranno trascurabili ...

Modifica (2011-06-06)

La mia esperienza su C # /. NET

Ora ho 5 mesi di codifica C # professionale quasi esclusiva (che si aggiunge al mio CV già pieno di C ++ e Java, e un tocco di C ++ / CLI).

Ho giocato con WinForms (Ahem ...) e WCF (cool!), E WPF (Cool !!!! Sia tramite XAML che raw C #. WPF è così facile che credo che Swing non possa essere paragonato ad esso) e C # 4.0.

La conclusione è che mentre è più facile / veloce produrre un codice che funzioni in C # / Java che in C ++, è molto più difficile produrre un codice forte, sicuro e robusto in C # (e ancora più difficile in Java) che in C ++. Le ragioni abbondano, ma si possono riassumere in:

  1. I generici non sono potenti quanto i modelli ( prova a scrivere un metodo Parse generico efficiente (dalla stringa a T) o un equivalente efficiente di boost :: lexical_cast in C # per capire il problema )
  2. RAII rimane impareggiabile ( GC può ancora perdere (sì, ho dovuto gestire quel problema) e gestirà solo la memoria. Anche il C # usingnon è così facile e potente perché scrivere una corretta implementazione di Dispose è difficile )
  3. C # readonlye Java finalnon sono utili quanto quelli di C ++const ( non c'è modo di esporre dati complessi di sola lettura (un albero di nodi, per esempio) in C # senza un lavoro eccezionale, mentre è una funzionalità incorporata di C ++. I dati immutabili sono una soluzione interessante , ma non tutto può essere reso immutabile, quindi non è nemmeno abbastanza, di gran lunga ).

Quindi, C # rimane un linguaggio piacevole fintanto che si desidera qualcosa che funzioni, ma un linguaggio frustrante nel momento in cui si desidera qualcosa che funzioni sempre e in sicurezza .

Java è ancora più frustrante, in quanto ha gli stessi problemi di C # e altro ancora: mancando l'equivalente della usingparola chiave di C # , un mio collega molto esperto ha trascorso troppo tempo assicurandosi che le sue risorse fossero liberate correttamente, mentre l'equivalente in C ++ avrebbe stato facile (usando distruttori e puntatori intelligenti).

Quindi immagino che l'aumento di produttività di C # / Java sia visibile per la maggior parte del codice ... fino al giorno in cui avrai bisogno che il codice sia il più perfetto possibile. Quel giorno conoscerai il dolore. (non crederai a ciò che viene richiesto dal nostro server e dalle app GUI ...).

Informazioni su Java lato server e C ++

Sono rimasto in contatto con i team server (ho lavorato 2 anni tra di loro, prima di tornare al team GUI), dall'altra parte dell'edificio, e ho imparato qualcosa di interessante.

Negli ultimi anni, la tendenza era che le app server Java fossero destinate a sostituire le vecchie app server C ++, poiché Java ha molti framework / strumenti ed è facile da mantenere, distribuire, ecc. Ecc.

... Fino a quando il problema della bassa latenza non ha sollevato la sua brutta testa negli ultimi mesi. Quindi, le app del server Java, indipendentemente dall'ottimizzazione tentata dal nostro esperto team Java, hanno perso la gara in modo semplice e pulito contro il vecchio server C ++ non ottimizzato.

Attualmente, la decisione è di mantenere i server Java per un uso comune in cui le prestazioni, pur essendo ancora importanti, non sono interessate dall'obiettivo di bassa latenza e ottimizzare in modo aggressivo le applicazioni server C ++ già più veloci per esigenze di bassa e bassissima latenza.

Conclusione

Niente è semplice come previsto.

Java, e ancora di più C #, sono linguaggi interessanti, con librerie e framework standard estesi, in cui è possibile programmare velocemente e ottenere risultati molto presto.

Ma quando hai bisogno di potenza pura, ottimizzazioni potenti e sistematiche, forte supporto per il compilatore, potenti funzionalità di linguaggio e sicurezza assoluta, Java e C # rendono difficile vincere le ultime percentuali di qualità mancanti ma critiche, devi rimanere al di sopra della concorrenza.

È come se avessi bisogno di meno tempo e sviluppatori meno esperti in C # / Java che in C ++ per produrre codice di qualità media, ma d'altra parte, nel momento in cui avevi bisogno di codice di qualità eccellente per perfezionare, è stato improvvisamente più facile e veloce ottenere i risultati proprio in C ++.

Naturalmente, questa è la mia percezione, forse limitata ai nostri bisogni specifici.

Tuttavia, è ciò che accade oggi, sia nei team della GUI che nei team lato server.

Ovviamente aggiornerò questo post se succede qualcosa di nuovo.

Modifica (2011-06-22)

"Troviamo che per quanto riguarda le prestazioni, C ++ vince con un ampio margine. Tuttavia, ha anche richiesto gli sforzi di ottimizzazione più estesi, molti dei quali sono stati eseguiti a un livello di sofisticazione che non sarebbe stato disponibile per il programmatore medio.

[...] La versione Java era probabilmente la più semplice da implementare, ma la più difficile da analizzare per le prestazioni. Nello specifico, gli effetti della raccolta dei rifiuti erano complicati e molto difficili da regolare ".

fonti:

Modifica (2011-09-20)

"La parola corrente su Facebook è che" il codice C ++ scritto in modo ragionevole funziona velocemente " , il che sottolinea l'enorme sforzo speso per ottimizzare PHP e codice Java. Paradossalmente, il codice C ++ è più difficile da scrivere che in altri linguaggi, ma il codice efficiente è un molto più facile [scrivere in C ++ che in altri linguaggi]. "

- Herb Sutter su // build / , citando Andrei Alexandrescu

fonti:


8
La modifica dopo 5 mesi di C # descrive esattamente la mia esperienza (modelli migliori, const migliori, RAII). +1. Queste tre rimangono le mie caratteristiche killer personali per C ++ (o D, per le quali non avevo ancora il tempo).
Sebastian Mach

"L'elaborazione del codice verrà eseguita in fase di compilazione". Quindi la metaprogrammazione del modello funziona solo nel programma è disponibile in fase di compilazione, il che spesso non è il caso, ad esempio è impossibile scrivere una libreria di espressioni regolari con prestazioni competitive in vanilla C ++ perché non è in grado di generare codice in fase di esecuzione (un aspetto importante di metaprogrammazione).
JD

"il gioco con i tipi viene fatto in fase di compilazione ... l'equivalente in Java o C # è al massimo doloroso da scrivere, e sarà sempre più lento e risolto in fase di runtime anche quando i tipi sono noti in fase di compilazione". In C #, ciò è vero solo per i tipi di riferimento e non è vero per i tipi di valore.
JD

1
"Indipendentemente dall'ottimizzazione JIT, niente andrà veloce come l'accesso diretto del puntatore alla memoria ... se hai dati contigui in memoria, accedendoti tramite puntatori C ++ (cioè puntatori C ... Diamo a Cesare ciò che è dovuto) andrà volte più veloce che in Java / C # ". Le persone hanno osservato Java battere C ++ nel test SOR dal benchmark SciMark2 proprio perché i puntatori impediscono le ottimizzazioni relative all'aliasing. blogs.oracle.com/dagastine/entry/sun_java_is_faster_than
JD

Vale anche la pena notare che .NET specializza i tipi di generici su librerie collegate dinamicamente dopo il collegamento, mentre C ++ non può perché i modelli devono essere risolti prima del collegamento. E ovviamente il grande vantaggio che i generici hanno sui modelli sono messaggi di errore comprensibili.
JD

48

Ogni volta che parlo di prestazioni gestite e non gestite, mi piace indicare la serie che Rico (e Raymond) hanno confrontato le versioni C ++ e C # di un dizionario cinese / inglese. Questa ricerca su Google ti permetterà di leggere da solo, ma mi piace il riassunto di Rico.

Quindi mi vergogno della mia schiacciante sconfitta? Difficilmente. Il codice gestito ha ottenuto un ottimo risultato praticamente senza alcuno sforzo. Per sconfiggere il gestito Raymond ha dovuto:

  • Scrive il proprio file I / O
  • Scrivi la sua classe di archi
  • Scrivi il suo allocatore
  • Scrivi la sua mappatura internazionale

Ovviamente ha usato le librerie di livello inferiore disponibili per farlo, ma è ancora molto lavoro. Puoi chiamare ciò che è rimasto un programma STL? Non credo, penso che abbia mantenuto la classe std :: vector che alla fine non è mai stato un problema e ha mantenuto la funzione find. Praticamente tutto il resto è andato.

Quindi, sì, puoi sicuramente battere il CLR. Raymond può rendere il suo programma ancora più veloce, credo.

È interessante notare che il tempo per analizzare il file come riportato dai timer interni di entrambi i programmi è all'incirca lo stesso: 30 ms per ciascuno. La differenza è nell'overhead.

Per me la linea di fondo è che sono state necessarie 6 revisioni per la versione non gestita per battere la versione gestita che era un semplice port del codice originale non gestito. Se hai bisogno di prestazioni fino all'ultimo bit (e hai il tempo e le competenze per ottenerle), dovrai andare non gestito, ma per me, prenderò il vantaggio dell'ordine di grandezza che ho sulle prime versioni rispetto alla 33 % Guadagno se provo 6 volte.


3
link is dead, trovato articolo menzionato qui: blogs.msdn.com/b/ricom/archive/2005/05/10/416151.aspx
gjvdkamp

Prima di tutto, se guardiamo il codice di Raymond Chen, chiaramente non capisce molto bene il C ++ o le strutture dati. Il suo codice raggiunge quasi direttamente il codice C di basso livello anche nei casi in cui il codice C non ha vantaggi in termini di prestazioni (sembra solo essere una sorta di sfiducia e forse una mancanza di conoscenza di come utilizzare i profiler). Inoltre non è riuscito a capire il modo più algoritmicamente corretto di implementare un dizionario (ha usato std :: find per l'amor di Dio). Se c'è qualcosa di buono in Java, Python, C #, ecc. - Tutti forniscono dizionari molto efficienti ...
stinky472

I tentativi o anche std :: map andrebbero molto più favorevolmente verso C ++ o anche una tabella hash. Infine, un dizionario è esattamente il tipo di programma che beneficia maggiormente di librerie e framework di alto livello. Non dimostra differenze nel linguaggio tanto quanto le librerie coinvolte (di cui, direi felicemente che C # è molto più completo e fornisce molti più strumenti adatti per l'attività). Mostra un programma che manipola grandi blocchi di memoria in confronto, come un codice matrice / vettoriale su larga scala. Questo risolverà abbastanza rapidamente anche se, come in questo caso, i programmatori non sanno cosa ...
stinky472

26

La compilazione per specifiche ottimizzazioni della CPU è generalmente sopravvalutata. Basta prendere un programma in C ++ e compilarlo con ottimizzazione per pentium PRO ed eseguirlo su un pentium 4. Quindi ricompilare con ottimizzazione per pentium 4. Ho passato lunghi pomeriggi a farlo con diversi programmi. Risultati generali ?? Di solito meno del 2-3% di aumento delle prestazioni. Quindi i vantaggi teorici della JIT sono quasi nulli. La maggior parte delle differenze di prestazioni possono essere osservate solo quando si utilizzano funzionalità di elaborazione dati scalari, cosa che alla fine richiederà una regolazione fine manuale per ottenere comunque le massime prestazioni. Ottimizzazioni di questo tipo sono lente e costose da eseguire, rendendole comunque a volte inadatte per JIT.

Nel mondo reale e nelle applicazioni reali, il C ++ è ancora solitamente più veloce di java, principalmente a causa del minore ingombro di memoria che si traduce in migliori prestazioni della cache.

Ma per utilizzare tutte le funzionalità C ++, lo sviluppatore deve lavorare sodo. Puoi ottenere risultati superiori, ma devi usare il cervello per quello. Il C ++ è un linguaggio che ha deciso di presentarti con più strumenti, addebitando il prezzo che devi imparare per poter usare bene il linguaggio.


4
Non è tanto che stai compilando per l'ottimizzazione della CPU, ma stai compilando per l'ottimizzazione del percorso di runtime. Se trovi che un metodo è molto spesso chiamato con un parametro specifico, potresti precompilare quella routine con quel parametro come una costante che potrebbe (nel caso di un booleano che controlla il flusso) escludere blocchi giganteschi di lavoro. Il C ++ non può avvicinarsi a questo tipo di ottimizzazione.
Bill K,

1
Quindi come fanno i JIT a ricompilare le routine per trarre vantaggio dai percorsi di esecuzione osservati e che differenza fa?
David Thornley

2
@ Bill Potrei mescolare due cose ... ma la previsione dei rami non viene eseguita in fase di esecuzione nella pipeline di istruzioni per raggiungere obiettivi simili indipendentemente dalla lingua?
Hardy

@ Hardy sì, la CPU può eseguire la predizione dei rami indipendentemente dalla lingua, ma non può escludere un intero ciclo osservando che il ciclo non ha alcun effetto su nulla. Inoltre non osserverà che mult (0) è programmato per restituire 0 e sostituire semplicemente l'intera chiamata al metodo con if (param == 0) result = 0; ed evitare l'intera chiamata di funzione / metodo. C potrebbe fare queste cose se il compilatore avesse una panoramica completa di ciò che stava accadendo, ma generalmente non ha abbastanza informazioni in fase di compilazione.
Bill K

21

JIT (Just In Time Compiling) può essere incredibilmente veloce perché ottimizza per la piattaforma di destinazione.

Ciò significa che può trarre vantaggio da qualsiasi trucco del compilatore che la tua CPU può supportare, indipendentemente dalla CPU su cui lo sviluppatore ha scritto il codice.

Il concetto di base di .NET JIT funziona in questo modo (fortemente semplificato):

Chiamare un metodo per la prima volta:

  • Il codice del tuo programma chiama un metodo Foo ()
  • Il CLR esamina il tipo che implementa Foo () e ottiene i metadati ad esso associati
  • Dai metadati, il CLR sa in quale indirizzo di memoria è memorizzato l'IL (Intermediate byte code).
  • Il CLR alloca un blocco di memoria e chiama il JIT.
  • Il JIT compila l'IL in codice nativo, lo inserisce nella memoria allocata e quindi cambia il puntatore alla funzione nei metadati di tipo Foo () in modo che punti a questo codice nativo.
  • Il codice nativo viene eseguito.

Chiamare un metodo per la seconda volta:

  • Il codice del tuo programma chiama un metodo Foo ()
  • Il CLR esamina il tipo che implementa Foo () e trova il puntatore a funzione nei metadati.
  • Il codice nativo in questa posizione di memoria viene eseguito.

Come puoi vedere, la seconda volta, è praticamente lo stesso processo del C ++, tranne che con il vantaggio delle ottimizzazioni in tempo reale.

Detto questo, ci sono ancora altri problemi generali che rallentano un linguaggio gestito, ma il JIT aiuta molto.


A proposito, Jonathan, penso che qualcuno stia ancora votando le tue cose. Quando ti ho votato avevi un -1 su questo post.
Brian R. Bondy,

12

Mi piace la risposta di Orion Adrian , ma c'è un altro aspetto.

La stessa domanda è stata posta decenni fa sul linguaggio assembly rispetto ai linguaggi "umani" come FORTRAN. E parte della risposta è simile.

Sì, un programma C ++ è in grado di essere più veloce di C # su un dato algoritmo (non banale?), Ma il programma in C # sarà spesso veloce o più veloce di un'implementazione "ingenua" in C ++ e di una versione ottimizzata in C ++ richiederà più tempo per lo sviluppo e potrebbe comunque battere la versione C # con un margine molto piccolo. Quindi ne vale davvero la pena?

Dovrai rispondere a questa domanda uno per uno.

Detto questo, sono un fan di lunga data del C ++ e penso che sia un linguaggio incredibilmente espressivo e potente, a volte sottovalutato. Ma in molti problemi della "vita reale" (per me personalmente, questo significa "il tipo per cui vengo pagato per risolvere"), C # farà il lavoro prima e in modo più sicuro.

La penalità più grande che paghi? Molti programmi .NET e Java sono divoratori di memoria. Ho visto app .NET e Java prendere "centinaia" di megabyte di memoria, quando programmi C ++ di complessità simile scalfiscono a malapena le "decine" di MB.


7

Non sono sicuro di quanto spesso scoprirai che il codice Java verrà eseguito più velocemente di C ++, anche con Hotspot, ma farò uno swing per spiegare come potrebbe accadere.

Pensa al codice Java compilato come linguaggio macchina interpretato per la JVM. Quando il processore Hotspot si accorge che alcune parti del codice compilato verranno utilizzate molte volte, esegue un'ottimizzazione sul codice macchina. Poiché la regolazione manuale dell'Assembly è quasi sempre più veloce del codice compilato C ++, è giusto immaginare che il codice macchina ottimizzato a livello di programmazione non sarà poi così male.

Quindi, per codice altamente ripetitivo, ho potuto vedere dove sarebbe possibile per Hotspot JVM eseguire Java più velocemente di C ++ ... fino a quando non entra in gioco la garbage collection. :)


Potresti approfondire l'affermazione Since hand-tuning Assembly is almost always faster than C++ compiled code? Cosa intendi per "assemblaggio manuale" e "codice compilato in C ++"?
paercebal

Bene, si basa sull'idea che l'ottimizzatore di un compilatore segue le regole e i programmatori no. Quindi ci sarà sempre codice che l'ottimizzatore scopre di non poter ottimizzare perfettamente, mentre un essere umano potrebbe, guardando un'immagine più grande o sapendo di più su ciò che il codice sta realmente facendo. Aggiungerò che questo è un commento di 3 anni fa, e so di più su HotSpot di quanto ne sapessi prima, e posso facilmente vedere l'ottimizzazione dinamica come un modo MOLTO carino per far funzionare il codice più velocemente.
billjamesdev

1. Le ottimizzazioni da Hotspot o qualsiasi altro JIT sono ancora ottimizzazioni del compilatore. JIT ha il vantaggio su un compilatore statico di essere in grado di inline alcuni risultati (codice spesso chiamato), o anche di fare ottimizzazioni basate sul processore in esecuzione, ma è ancora un'ottimizzazione del compilatore. . . 2. Immagino che tu stia parlando di ottimizzazione dell'algoritmo, non di "regolazione fine dell'assemblaggio". La "regolazione fine dell'assemblaggio manuale da parte di un programmatore umano" non è riuscita a produrre risultati migliori rispetto alle ottimizzazioni del compilatore da oltre un decennio. In effetti, un essere umano che gioca con l'assemblaggio di solito
rovina

Ok, ho capito che sto usando la terminologia sbagliata, "ottimizzazione del compilatore" piuttosto che "ottimizzazione statica". Vorrei sottolineare che, almeno nell'industria dei giochi, di recente come per la PS2 stavamo ancora usando assemblaggi codificati a mano in posti per "ottimizzare" per i chip specifici che sapevamo fossero sulla console; cross-compilatori per questi nuovi chip non essendo ancora sofisticati come quelli per le architetture x86. Tornando alla domanda originale sopra: la JIT ha il vantaggio di essere in grado di misurare prima dell'ottimizzazione, che è una buona cosa (TM)
billjamesdev

Nota che la maggior parte dei GC di produzione utilizza anche l'assemblatore scritto a mano perché C / C ++ non lo taglia.
JD

6

In generale, l' algoritmo del tuo programma sarà molto più importante per la velocità della tua applicazione rispetto al linguaggio . Puoi implementare un algoritmo scadente in qualsiasi linguaggio, incluso C ++. Con questo in mente, sarai generalmente in grado di scrivere il codice più velocemente in un linguaggio che ti aiuta a implementare un algoritmo più efficiente.

I linguaggi di livello superiore fanno molto bene in questo, fornendo un accesso più facile a molte strutture di dati pre-costruite efficienti e incoraggiando pratiche che ti aiuteranno a evitare codice inefficiente. Naturalmente, a volte possono anche rendere facile scrivere un sacco di codice molto lento, quindi devi ancora conoscere la tua piattaforma.

Inoltre, C ++ sta recuperando il ritardo con "nuove" (notare le virgolette) funzionalità come i contenitori STL, i puntatori automatici, ecc - vedere la libreria boost, per esempio. E potresti occasionalmente scoprire che il modo più veloce per eseguire alcune attività richiede una tecnica come l'aritmetica dei puntatori che è vietata in un linguaggio di livello superiore, sebbene tipicamente ti consentano di chiamare una libreria scritta in un linguaggio che può implementarla come desiderato .

La cosa principale è conoscere la lingua che stai utilizzando, l'API associata, cosa può fare e quali sono i suoi limiti.


5

Non lo so neanche io ... i miei programmi Java sono sempre lenti. :-) Non ho mai notato che i programmi C # siano particolarmente lenti, però.


4

Ecco un altro benchmark interessante, che puoi provare sul tuo computer.

Confronta ASM, VC ++, C #, Silverlight, applet Java, Javascript, Flash (AS3)

Demo della velocità del plug-in Roozz

Si noti che la velocità di javascript varia molto a seconda del browser che lo esegue. Lo stesso vale per Flash e Silverlight perché questi plug-in vengono eseguiti nello stesso processo del browser di hosting. Ma il plug-in Roozz esegue file .exe standard, che vengono eseguiti nel proprio processo, quindi la velocità non è influenzata dal browser di hosting.


4

Dovresti definire "prestazioni migliori di ..". Beh, lo so, hai chiesto della velocità, ma non è tutto ciò che conta.

  • Le macchine virtuali eseguono più overhead di runtime? Sì!
  • Mangiano più memoria di lavoro? Sì!
  • Hanno costi di avvio più elevati (inizializzazione runtime e compilatore JIT)? Sì!
  • Necessitano di un'enorme libreria installata? Sì!

E così via, è di parte, sì;)

Con C # e Java paghi un prezzo per quello che ottieni (codifica più veloce, gestione automatica della memoria, grande libreria e così via). Ma non hai molto spazio per contrattare sui dettagli: prendi il pacchetto completo o niente.

Anche se questi linguaggi possono ottimizzare parte del codice per eseguire più velocemente del codice compilato, l'intero approccio è (IMHO) inefficiente. Immagina di guidare ogni giorno per 5 miglia fino al tuo posto di lavoro, con un camion! È comodo, si sente bene, sei al sicuro (zona di estrema deformazione) e dopo aver premuto il gas per un po 'di tempo, sarà anche veloce come un'auto standard! Perché non abbiamo tutti un camion da guidare per andare al lavoro? ;)

In C ++ ottieni quello per cui paghi, non di più, non di meno.

Citando Bjarne Stroustrup: "C ++ è il mio linguaggio preferito per la raccolta dei rifiuti perché genera così poca spazzatura" testo del collegamento


Beh, penso che abbia una buona idea dei suoi svantaggi, ha anche detto: "C rende facile spararsi al piede; C ++ lo rende più difficile, ma quando lo fai ti fa saltare tutta la gamba";)
Frunsi

"Richiedono un'enorme libreria installata" Java sta affrontando questo problema con il puzzle del progetto, credo.
toc777

"In C ++ ottieni quello per cui paghi, non di più, non di meno". Contro esempio: ho confrontato un'implementazione di un albero RB in OCaml e C ++ (GNU GCC) che utilizzava un'eccezione per saltare in lungo fuori dalla ricorsione se un elemento che veniva aggiunto era già presente per riutilizzare l'insieme esistente. OCaml era fino a 6 volte più veloce di C ++ perché non paga per il controllo dei distruttori mentre lo stack viene svolto.
JD

3
@ Jon: ma ad un certo punto (più tardi?) Nel tempo deve comunque distruggere gli oggetti (almeno deve liberare la sua memoria). E nota anche che le eccezioni sono per casi eccezionali, almeno in C ++ quella regola dovrebbe essere rispettata. Le eccezioni C ++ possono essere pesanti quando si verificano eccezioni, questo è un compromesso.
Frunsi

@ Jon: forse prova a ripetere il tuo benchmark con timessu una shell. In modo che controlli l'intero programma, non solo un singolo aspetto. I risultati sono quindi simili?
Frunsi

3

Il codice eseguibile prodotto da un compilatore Java o C # non viene interpretato: viene compilato in codice nativo "just in time" (JIT). Quindi, il primo time code in un programma Java / C # viene rilevato durante l'esecuzione, c'è un certo sovraccarico poiché il "compilatore runtime" (noto anche come compilatore JIT) trasforma il codice byte (Java) o il codice IL (C #) in istruzioni macchina native. Tuttavia, la prossima volta che viene rilevato il codice mentre l'applicazione è ancora in esecuzione, il codice nativo viene eseguito immediatamente. Questo spiega come alcuni programmi Java / C # sembrano inizialmente lenti, ma poi funzionano meglio più a lungo vengono eseguiti. Un buon esempio è un sito web ASP.Net. La prima volta che si accede al sito Web, potrebbe essere un po 'più lento poiché il codice C # viene compilato in codice nativo dal compilatore JIT.


3

Alcune buone risposte qui sulla domanda specifica che hai posto. Vorrei fare un passo indietro e guardare il quadro più ampio.

Tieni presente che la percezione dell'utente della velocità del software che scrivi è influenzata da molti altri fattori oltre a quanto bene il codegen si ottimizza. Ecco alcuni esempi:

  • La gestione manuale della memoria è difficile da eseguire correttamente (nessuna perdita) e ancora più difficile da eseguire in modo efficiente (libera la memoria subito dopo averlo finito). L'uso di un GC è, in generale, più probabile che produca un programma che gestisce bene la memoria. Sei disposto a lavorare molto duramente e ritardare la consegna del software, nel tentativo di superare il GC?

  • Il mio C # è più facile da leggere e capire del mio C ++. Ho anche più modi per convincermi che il mio codice C # funzioni correttamente. Ciò significa che posso ottimizzare i miei algoritmi con meno rischi di introdurre bug (e agli utenti non piace il software che si arresta in modo anomalo, anche se lo fa rapidamente!)

  • Posso creare il mio software più velocemente in C # che in C ++. Ciò consente di risparmiare tempo per lavorare sulle prestazioni e fornire comunque il mio software in tempo.

  • È più facile scrivere una buona interfaccia utente in C # che in C ++, quindi è più probabile che sia in grado di spostare il lavoro in background mentre l'interfaccia utente rimane reattiva o di fornire progressi o un'interfaccia utente a ritmo di ascolto quando il programma deve bloccarsi per un po '. Questo non rende nulla più veloce, ma rende gli utenti più felici dell'attesa.

Tutto quello che ho detto su C # è probabilmente vero per Java, semplicemente non ho l'esperienza per dirlo con certezza.


3

Se sei un programmatore Java / C # che impara C ++, sarai tentato di continuare a pensare in termini di Java / C # e tradurre alla lettera la sintassi C ++. In tal caso, si ottengono solo i vantaggi menzionati in precedenza del codice nativo rispetto a quello interpretato / JIT. Per ottenere il massimo guadagno di prestazioni in C ++ rispetto a Java / C #, devi imparare a pensare in C ++ e progettare codice in modo specifico per sfruttare i punti di forza di C ++.

Parafrasando Edsger Dijkstra : [la tua prima lingua] mutila la mente oltre il recupero.
Parafrasando Jeff Atwood : puoi scrivere [la tua prima lingua] in qualsiasi nuova lingua.


1
Sospetto che il detto "Puoi scrivere FORTRAN in qualsiasi lingua" sia anteriore alla carriera di Jeff.
David Thornley

3

Una delle ottimizzazioni JIT più significative è il metodo inlining. Java può anche incorporare metodi virtuali se può garantire la correttezza del runtime. Questo tipo di ottimizzazione di solito non può essere eseguito dai compilatori statici standard perché richiede l'analisi dell'intero programma, il che è difficile a causa della compilazione separata (al contrario, JIT ha tutto il programma disponibile). Il metodo inlining migliora altre ottimizzazioni, fornendo blocchi di codice più grandi da ottimizzare.

Anche l'allocazione della memoria standard in Java / C # è più veloce e la deallocazione (GC) non è molto più lenta, ma solo meno deterministica.


Si noti che freee deletenon sono deterministici e GC può essere reso deterministico non allocando.
JD

3

È improbabile che i linguaggi della macchina virtuale superino i linguaggi compilati, ma possono avvicinarsi abbastanza da non avere importanza, per (almeno) i seguenti motivi (sto parlando per Java qui poiché non ho mai fatto C #).

1 / Java Runtime Environment è solitamente in grado di rilevare parti di codice che vengono eseguite frequentemente ed eseguire la compilazione just-in-time (JIT) di tali sezioni in modo che, in futuro, vengano eseguite alla massima velocità di compilazione.

2 / Vaste porzioni delle librerie Java sono compilate in modo che, quando chiami una funzione di libreria, stai eseguendo codice compilato, non interpretato. Puoi vedere il codice (in C) scaricando OpenJDK.

3 / A meno che tu non stia facendo calcoli enormi, la maggior parte del tempo in cui il tuo programma è in esecuzione, attende l'input da un essere umano molto lento (relativamente parlando).

4 / Poiché gran parte della convalida del bytecode Java viene eseguita al momento del caricamento della classe, il normale sovraccarico dei controlli di runtime è notevolmente ridotto.

5 / Nel peggiore dei casi, il codice ad alta intensità di prestazioni può essere estratto in un modulo compilato e chiamato da Java (vedere JNI) in modo che funzioni a piena velocità.

In sintesi, il bytecode Java non supererà mai il linguaggio macchina nativo, ma ci sono modi per mitigarlo. Il grande vantaggio di Java (per come la vedo io) è l' ENORME libreria standard e la natura multipiattaforma.


1
Per l'elemento 2, "2 / Vaste porzioni delle librerie Java sono compilate in modo che, quando chiami una funzione di libreria, stai eseguendo codice compilato, non interpretato": Hai una citazione per questo? Se fosse davvero come descrivi, mi aspetterei di imbattermi molto nel codice nativo del mio debugger, ma non lo faccio.
cero

I debugger di Re: cero spesso utilizzano percorsi meno efficienti ma più espressivi e quindi non sono un buon indicatore per qualsiasi cosa relativa alle prestazioni.
Guvante

2
C'è un altro enorme guadagno di prestazioni in questa libreria HUGH: il codice della libreria è probabilmente scritto meglio di quello che molti programmatori scriveranno da soli (dato un tempo limitato e la mancanza di conoscenze specialistiche) e su Java, a causa di molte ragioni, i programmatori spesso usano la Biblioteca.
Liran Orevi

3

Orion Adrian , permettimi di invertire il tuo post per vedere quanto siano infondate le tue osservazioni, perché si può dire molto anche sul C ++. E dire che il compilatore Java / C # ottimizza le funzioni vuote ti fa davvero sembrare che tu non sia il mio esperto di ottimizzazione, perché a) perché un programma reale dovrebbe contenere funzioni vuote, ad eccezione di codice legacy davvero pessimo, b) che non lo è davvero ottimizzazione del nero e del bleeding edge.

A parte questa frase, hai parlato apertamente dei puntatori, ma gli oggetti in Java e C # non funzionano fondamentalmente come i puntatori C ++? Non possono sovrapporsi? Non possono essere nulli? C (e la maggior parte delle implementazioni C ++) ha la parola chiave limits, entrambi hanno tipi di valore, C ++ ha un riferimento al valore con garanzia non nulla. Cosa offrono Java e C #?

>>>>>>>>>>

In generale, C e C ++ possono essere altrettanto veloci o più veloci perché il compilatore AOT, un compilatore che compila il codice prima della distribuzione, una volta per tutte, su molti server di build core con memoria elevata, può eseguire ottimizzazioni che un programma compilato C # non può perché ha un sacco di tempo per farlo. Il compilatore può determinare se la macchina è Intel o AMD; Pentium 4, Core Solo o Core Duo; o se supporta SSE4, ecc. e se il tuo compilatore non supporta l'invio di runtime, puoi risolverlo da solo distribuendo una manciata di binari specializzati.

Il programma AC # viene comunemente compilato dopo averlo eseguito in modo che funzioni decentemente bene su tutte le macchine, ma non è ottimizzato quanto potrebbe essere per una singola configurazione (cioè processore, set di istruzioni, altro hardware) e deve impiegare un po 'di tempo primo. Funzionalità come la fissione del ciclo, l'inversione del ciclo, la vettorizzazione automatica, l'ottimizzazione dell'intero programma, l'espansione del modello, l'IPO e molte altre, sono molto difficili da risolvere completamente e completamente in un modo che non infastidisce l'utente finale.

Inoltre, alcune funzionalità del linguaggio consentono al compilatore in C ++ o C di fare ipotesi sul codice che gli consentono di ottimizzare alcune parti che non sono sicure per il compilatore Java / C #. Quando non si ha accesso all'ID di tipo completo dei generici oa un flusso di programma garantito, ci sono molte ottimizzazioni che non sono sicure.

Anche C ++ e C eseguono molte allocazioni di stack contemporaneamente con un solo incremento di registro, il che è sicuramente più efficiente delle allocazioni Javas e C # per quanto riguarda il livello di astrazione tra il garbage collector e il codice.

Ora non posso parlare per Java su questo punto successivo, ma so che i compilatori C ++, ad esempio, rimuoveranno effettivamente metodi e chiamate di metodo quando sa che il corpo del metodo è vuoto, eliminerà le sottoespressioni comuni, potrebbe provare e riprovare per trovare un utilizzo ottimale dei registri, non impone il controllo dei limiti, autovettorizzerà i loop e i loop interni e invertirà gli interni in quelli esterni, sposta i condizionali fuori dai loop, divide e non divide i loop. Espanderà std :: vector in array nativi zero overhead come faresti nel modo C. Farà ottimizzazioni inter procedurali. Costruirà i valori di ritorno direttamente nel sito del chiamante. Piegherà e propagherà le espressioni. Riordinerà i dati in modo semplice per la cache. Farà jump threading. Ti consente di scrivere time ray traccianti di compilazione con zero overhead di runtime. Realizzerà ottimizzazioni basate su grafici molto costose. Ridurrà la forza, se sostituisse alcuni codici con codice sintatticamente totalmente disuguale ma semanticamente equivalente (il vecchio "xor foo, foo" è solo l'ottimizzazione più semplice, sebbene obsoleta di questo tipo). Se lo chiedi gentilmente, puoi omettere gli standard in virgola mobile IEEE e abilitare ancora più ottimizzazioni come il riordino degli operandi in virgola mobile. Dopo che ha massaggiato e massacrato il tuo codice, potrebbe ripetere l'intero processo, perché spesso alcune ottimizzazioni gettano le basi per ottimizzazioni ancora più certe. Potrebbe anche semplicemente riprovare con parametri mescolati e vedere come l'altra variante ottiene il punteggio nella sua classifica interna. E utilizzerà questo tipo di logica in tutto il codice. dove sostituisce alcuni codici con codice sintatticamente totalmente disuguale ma semanticamente equivalente (il vecchio "xor pippo, pippo" è solo l'ottimizzazione più semplice, sebbene obsoleta di questo tipo). Se lo chiedi gentilmente, puoi omettere gli standard in virgola mobile IEEE e abilitare ancora più ottimizzazioni come il riordino degli operandi in virgola mobile. Dopo che ha massaggiato e massacrato il tuo codice, potrebbe ripetere l'intero processo, perché spesso alcune ottimizzazioni gettano le basi per ottimizzazioni ancora più certe. Potrebbe anche semplicemente riprovare con parametri mescolati e vedere come l'altra variante ottiene il punteggio nella sua classifica interna. E utilizzerà questo tipo di logica in tutto il codice. dove sostituisce alcuni codici con codice sintatticamente totalmente disuguale ma semanticamente equivalente (il vecchio "xor pippo, pippo" è solo l'ottimizzazione più semplice, sebbene obsoleta di questo tipo). Se lo chiedi gentilmente, puoi omettere gli standard in virgola mobile IEEE e abilitare ancora più ottimizzazioni come il riordino degli operandi in virgola mobile. Dopo che ha massaggiato e massacrato il tuo codice, potrebbe ripetere l'intero processo, perché spesso alcune ottimizzazioni gettano le basi per ottimizzazioni ancora più certe. Potrebbe anche semplicemente riprovare con parametri mescolati e vedere come l'altra variante ottiene il punteggio nella sua classifica interna. E utilizzerà questo tipo di logica in tutto il codice. Se lo chiedi gentilmente, puoi omettere gli standard in virgola mobile IEEE e abilitare ancora più ottimizzazioni come il riordino degli operandi in virgola mobile. Dopo che ha massaggiato e massacrato il tuo codice, potrebbe ripetere l'intero processo, perché spesso alcune ottimizzazioni gettano le basi per ottimizzazioni ancora più certe. Potrebbe anche semplicemente riprovare con parametri mescolati e vedere come l'altra variante ottiene il punteggio nella sua classifica interna. E utilizzerà questo tipo di logica in tutto il codice. Se lo chiedi gentilmente, puoi omettere gli standard in virgola mobile IEEE e abilitare ancora più ottimizzazioni come il riordino degli operandi in virgola mobile. Dopo che ha massaggiato e massacrato il tuo codice, potrebbe ripetere l'intero processo, perché spesso alcune ottimizzazioni gettano le basi per ottimizzazioni ancora più certe. Potrebbe anche semplicemente riprovare con parametri mescolati e vedere come l'altra variante ottiene il punteggio nella sua classifica interna. E utilizzerà questo tipo di logica in tutto il codice. Potrebbe anche semplicemente riprovare con parametri mescolati e vedere come l'altra variante ottiene il punteggio nella sua classifica interna. E utilizzerà questo tipo di logica in tutto il codice. Potrebbe anche semplicemente riprovare con parametri mescolati e vedere come l'altra variante ottiene il punteggio nella sua classifica interna. E utilizzerà questo tipo di logica in tutto il codice.

Quindi, come puoi vedere, ci sono molti motivi per cui alcune implementazioni C ++ o C saranno più veloci.

Detto questo, molte ottimizzazioni possono essere fatte in C ++ che spazzeranno via tutto ciò che potresti fare con C #, specialmente nel regno del numero, in tempo reale e vicino al metallo, ma non esclusivamente lì. Non devi nemmeno toccare un singolo puntatore per fare molta strada.

Quindi, a seconda di cosa stai scrivendo, preferisco l'uno o l'altro. Ma se stai scrivendo qualcosa che non dipende dall'hardware (driver, videogioco, ecc.), Non mi preoccuperei delle prestazioni di C # (di nuovo non posso parlare di Java). Andrà benissimo.

<<<<<<<<<<

In generale, alcuni argomenti generalizzati potrebbero sembrare interessanti in post specifici, ma generalmente non sembrano certamente credibili.

Comunque, per fare pace: AOT è fantastico, così come JIT . L'unica risposta corretta può essere: dipende. E le persone veramente intelligenti sanno che puoi comunque utilizzare il meglio di entrambi i mondi.


2

Succederebbe solo se l'interprete Java producesse codice macchina che è effettivamente meglio ottimizzato del codice macchina che il compilatore sta generando per il codice C ++ che stai scrivendo, al punto in cui il codice C ++ è più lento di Java e il costo di interpretazione.

Tuttavia, le probabilità che ciò accada sono piuttosto basse, a meno che forse Java non abbia una libreria scritta molto bene e tu abbia la tua libreria C ++ scritta male.


Credo anche che ci sia anche un certo peso linguistico, quando si lavora a un livello inferiore, con meno astrazione, si svilupperà un programma più veloce. Questo non è correlato ai punti sull'esecuzione del bytecode stesso.
Brian R. Bondy,

2

In realtà, C # non viene eseguito in una macchina virtuale come fa Java. IL è compilato in linguaggio assembly, che è interamente codice nativo e viene eseguito alla stessa velocità del codice nativo. È possibile eseguire il pre-JIT di un'applicazione .NET che rimuove completamente il costo JIT e quindi eseguire il codice completamente nativo.

Il rallentamento con .NET non avverrà perché il codice .NET è più lento, ma perché fa molto di più dietro le quinte per fare cose come la raccolta dei rifiuti, il controllo dei riferimenti, l'archiviazione di stack frame completi, ecc. Questo può essere abbastanza potente e utile quando creare applicazioni, ma ha anche un costo. Nota che puoi fare tutte queste cose anche in un programma C ++ (molte delle funzionalità principali di .NET sono in realtà codice .NET che puoi visualizzare in ROTOR). Tuttavia, se scrivessi a mano la stessa funzionalità, probabilmente ti ritroveresti con un programma molto più lento poiché il runtime .NET è stato ottimizzato e messo a punto con precisione.

Detto questo, uno dei punti di forza del codice gestito è che può essere completamente verificabile, ad es. puoi verificare che il codice non accederà mai alla memoria di un altro processo o eseguirà operazioni non salvate prima di eseguirlo. Microsoft ha un prototipo di ricerca di un sistema operativo completamente gestito che ha sorprendentemente dimostrato che un ambiente gestito al 100% può effettivamente funzionare molto più velocemente di qualsiasi sistema operativo moderno, sfruttando questa verifica per disattivare le funzionalità di sicurezza che non sono più necessarie ai programmi gestiti (stiamo parlando come 10x in alcuni casi). La radio SE ha un grande episodio che parla di questo progetto.


1

In alcuni casi, il codice gestito può effettivamente essere più veloce del codice nativo. Ad esempio, gli algoritmi di garbage collection "mark-and-sweep" consentono ad ambienti come JRE o CLR di liberare un gran numero di oggetti di breve durata (solitamente) in un unico passaggio, dove la maggior parte degli oggetti heap C / C ++ vengono liberati uno alla volta un tempo.

Da wikipedia :

Per molti scopi pratici, gli algoritmi ad alta intensità di allocazione / deallocazione implementati nei linguaggi Garbage Collection possono effettivamente essere più veloci dei loro equivalenti utilizzando l'allocazione manuale dell'heap. Uno dei motivi principali di ciò è che il Garbage Collector consente al sistema di runtime di ammortizzare le operazioni di allocazione e deallocazione in modo potenzialmente vantaggioso.

Detto questo, ho scritto molto C # e molto C ++ e ho eseguito molti benchmark. Nella mia esperienza, C ++ è molto più veloce di C #, in due modi: (1) se prendi del codice che hai scritto in C #, portalo in C ++ il codice nativo tende ad essere più veloce. Quanto più velocemente? Bene, varia molto, ma non è raro vedere un miglioramento della velocità del 100%. (2) In alcuni casi, la raccolta dei rifiuti può massicciamente rallentare un'applicazione gestita. .NET CLR fa un pessimo lavoro con grandi quantità di risorse (ad esempio,> 2 GB) e può finire per passare molto tempo in GC, anche in applicazioni che hanno pochi o addirittura nessun oggetto di durata di vita intermedia.

Naturalmente, nella maggior parte dei casi che ho riscontrato, i linguaggi gestiti sono abbastanza veloci, di gran lunga, e il compromesso di manutenzione e codifica per le prestazioni extra di C ++ semplicemente non è buono.


1
Il problema è che per processi a lunga esecuzione, come un server web, la tua memoria nel tempo diventerà così frammentata (in un programma scritto in C ++) che dovrai implementare qualcosa che assomigli alla garbage collection (o riavviare ogni tanto, vedi IIS ).
Tony BenBrahim,

3
Non l'ho notato sui grandi programmi Unix che dovrebbero funzionare per sempre. Tendono ad essere scritti in C, che è anche peggio per la gestione della memoria rispetto al C ++.
David Thornley

Ovviamente, la domanda è se stiamo confrontando un'implementazione di un programma in codice gestito e non gestito o le prestazioni teoriche massime del linguaggio. Chiaramente, il codice non gestito può sempre essere veloce almeno quanto gestito, poiché nel peggiore dei casi potresti semplicemente scrivere un programma non gestito che ha esattamente la stessa cosa del codice gestito! Ma la maggior parte dei problemi di prestazioni sono algoritmici, non micro. Inoltre, non ottimizzi allo stesso modo il codice gestito e non gestito, quindi "C ++ in C #" di solito non funzionerà bene.
kyoryu

2
In C / C ++ puoi allocare oggetti di breve durata nello stack e lo fai quando è appropriato. Nel codice gestito non puoi , non hai scelta. Inoltre, in C / C ++ puoi allocare elenchi di oggetti in aree contigue (nuovo Foo [100]), nel codice gestito non puoi. Quindi, il tuo confronto non è valido. Ebbene, questo potere di scelta pone un peso sugli sviluppatori, ma in questo modo imparano a conoscere il mondo in cui vivono (memoria ...).
Frunsi

@frunsi: "in C / C ++ puoi allocare elenchi di oggetti in aree contigue (nuovo Foo [100]), nel codice gestito non puoi". Non è corretto. I tipi di valore locale sono allocati nello stack e puoi persino impilare allocare array di essi in C #. Esistono anche sistemi di produzione scritti in C # che sono completamente privi di allocazione nello stato stazionario.
JD


1

In realtà, la JVM HotSpot di Sun utilizza l'esecuzione in "modalità mista". Interpreta il bytecode del metodo finché non determina (di solito tramite un contatore di qualche tipo) che un particolare blocco di codice (metodo, ciclo, blocco try-catch, ecc.) Verrà eseguito molto, quindi lo compila JIT. Il tempo richiesto per la compilazione JIT di un metodo spesso richiede più tempo rispetto a se il metodo dovesse essere interpretato se è un metodo eseguito di rado. Le prestazioni sono generalmente più elevate per la "modalità mista" perché la JVM non perde tempo a eseguire il JIT di codice che viene eseguito raramente, se non mai. C # e .NET non lo fanno. .NET JIT è tutto ciò che, spesso, fa perdere tempo.


1

Leggi di più su Dynamo di HP Labs , un interprete per PA-8000 che gira su PA-8000 e spesso esegue programmi più velocemente di quanto non facciano in modo nativo. Allora non sembrerà affatto sorprendente!

Non pensarlo come un "passaggio intermedio": l'esecuzione di un programma implica già molti altri passaggi, in qualsiasi lingua.

Spesso si tratta di:

  • programmi hanno punti caldi, quindi anche se stai eseguendo più lentamente il 95% del corpo di codice che devi eseguire, puoi comunque essere competitivo in termini di prestazioni se sei più veloce al 5% caldo

  • un HLL sa di più sul tuo intento di un LLL come C / C ++, e quindi può generare codice più ottimizzato (OCaml ne ha ancora di più, e in pratica è spesso anche più veloce)

  • un compilatore JIT ha molte informazioni che un compilatore statico non ha (come i dati effettivi che ti capita di avere questa volta)

  • un compilatore JIT può eseguire ottimizzazioni in fase di esecuzione che i linker tradizionali non sono realmente autorizzati a fare (come riordinare i rami in modo che il caso comune sia piatto o chiamate di libreria inlining)

Tutto sommato, C / C ++ sono linguaggi piuttosto scadenti per le prestazioni: ci sono relativamente poche informazioni sui tuoi tipi di dati, nessuna informazione sui tuoi dati e nessun runtime dinamico per consentire molto in termini di ottimizzazione del runtime.


1

Potresti ottenere brevi raffiche quando Java o CLR è più veloce di C ++, ma nel complesso le prestazioni sono peggiori per la vita dell'applicazione: vedi www.codeproject.com/KB/dotnet/RuntimePerformance.aspx per alcuni risultati per questo.



1

La mia comprensione è che C / C ++ produce codice nativo da eseguire su una particolare architettura della macchina. Al contrario, linguaggi come Java e C # vengono eseguiti su una macchina virtuale che astrae l'architettura nativa. Logicamente sembrerebbe impossibile per Java o C # eguagliare la velocità di C ++ a causa di questo passaggio intermedio, tuttavia mi è stato detto che gli ultimi compilatori ("hot spot") possono raggiungere questa velocità o addirittura superarla.

Questo è illogico. L'uso di una rappresentazione intermedia non degrada intrinsecamente le prestazioni. Ad esempio, llvm-gcc compila C e C ++ tramite LLVM IR (che è una macchina virtuale a registri infiniti) in codice nativo e raggiunge prestazioni eccellenti (spesso battendo GCC).

Forse questa è più una domanda del compilatore che una domanda sulla lingua, ma qualcuno può spiegare in un inglese semplice come è possibile che uno di questi linguaggi della macchina virtuale funzioni meglio di una lingua madre?

Ecco alcuni esempi:

  • Le macchine virtuali con compilazione JIT facilitano la generazione di codice in fase di esecuzione (ad esempio System.Reflection.Emitsu .NET) in modo da poter compilare il codice generato al volo in linguaggi come C # e F # ma è necessario ricorrere alla scrittura di un interprete relativamente lento in C o C ++. Ad esempio, per implementare espressioni regolari.

  • Parti della macchina virtuale (ad esempio la barriera di scrittura e l'allocatore) sono spesso scritte in un assemblatore codificato a mano perché C e C ++ non generano codice abbastanza veloce. Se un programma mette in rilievo queste parti di un sistema, allora potrebbe plausibilmente sovraperformare qualsiasi cosa possa essere scritta in C o C ++.

  • Il collegamento dinamico del codice nativo richiede la conformità a un ABI che può impedire le prestazioni e ovviare all'ottimizzazione dell'intero programma, mentre il collegamento è in genere differito sulle VM e può trarre vantaggio dalle ottimizzazioni dell'intero programma (come i generici reificati di .NET).

Vorrei anche affrontare alcuni problemi con la risposta altamente votata di paercebal sopra (perché qualcuno continua a cancellare i miei commenti sulla sua risposta) che presenta una visione polarizzata in modo controproducente:

L'elaborazione del codice verrà eseguita al momento della compilazione ...

Quindi la metaprogrammazione del modello funziona solo se il programma è disponibile in fase di compilazione, il che spesso non è il caso, ad esempio è impossibile scrivere una libreria di espressioni regolari con prestazioni competitive in vanilla C ++ perché non è in grado di generare codice in fase di esecuzione (un aspetto importante di metaprogrammazione).

... giocare con i tipi viene fatto in fase di compilazione ... l'equivalente in Java o C # è doloroso nella migliore delle ipotesi da scrivere, e sarà sempre più lento e risolto in fase di esecuzione anche quando i tipi sono noti in fase di compilazione.

In C #, ciò è vero solo per i tipi di riferimento e non è vero per i tipi di valore.

Indipendentemente dall'ottimizzazione JIT, niente andrà veloce come l'accesso diretto del puntatore alla memoria ... se hai dati contigui in memoria, accedervi tramite puntatori C ++ (cioè puntatori C ... Diamo a Caesar ciò che è dovuto) andrà molto più velocemente rispetto a Java / C #.

Le persone hanno osservato Java battere C ++ nel test SOR dal benchmark SciMark2 proprio perché i puntatori impediscono le ottimizzazioni relative all'aliasing.

Vale anche la pena notare che .NET specializza i tipi di generici su librerie collegate dinamicamente dopo il collegamento, mentre C ++ non può perché i modelli devono essere risolti prima del collegamento. E ovviamente il grande vantaggio che i generici hanno sui modelli sono messaggi di errore comprensibili.


0

Oltre a quello che hanno detto altri, dalla mia comprensione .NET e Java sono migliori nell'allocazione della memoria. Ad esempio, possono compattare la memoria man mano che viene frammentata mentre C ++ non può (in modo nativo, ma può farlo se stai usando un garbage collector intelligente).


O se stai usando un allocatore C ++ migliore e / o un pool di oggetti. Questo è tutt'altro che magico, da un punto di vista C ++, e può ridursi a fare in modo che "l'allocazione dell'heap" diventi altrettanto velocemente un'allocazione dello stack.
paercebal

Se allocherai sempre tutto sull'heap, .NET e Java potrebbero persino funzionare meglio di C / C ++. Ma semplicemente non lo farai in C / C ++.
Frunsi

0

Per tutto ciò che richiede molta velocità, la JVM chiama solo un'implementazione C ++, quindi è più una questione di quanto siano buone le loro librerie che di quanto sia buona la JVM per la maggior parte delle cose relative al sistema operativo. La garbage collection dimezza la memoria, ma l'utilizzo di alcune delle più elaborate funzionalità STL e Boost avrà lo stesso effetto ma con molte volte il potenziale di bug.

Se stai usando solo le librerie C ++ e molte delle sue funzionalità di alto livello in un grande progetto con molte classi, probabilmente finirai più lentamente rispetto all'utilizzo di una JVM. Tranne che molto più incline agli errori.

Tuttavia, il vantaggio di C ++ è che ti consente di ottimizzare te stesso, altrimenti sei bloccato con ciò che fa il compilatore / jvm. Se crei i tuoi contenitori, scrivi la tua gestione della memoria allineata, usa SIMD e passa all'assembly qua e là, puoi accelerare almeno 2x-4x volte rispetto a quello che la maggior parte dei compilatori C ++ farà da sola. Per alcune operazioni, 16x-32x. Utilizzando gli stessi algoritmi, se si utilizzano algoritmi migliori e si parallelizza, gli aumenti possono essere notevoli, a volte migliaia di volte più veloci dei metodi comunemente utilizzati.


0

Lo guardo da alcuni punti diversi.

  1. Dato il tempo e le risorse infiniti, il codice gestito o non gestito sarà più veloce? Chiaramente, la risposta è che il codice non gestito può sempre legare almeno il codice gestito in questo aspetto, poiché nel caso peggiore, dovresti semplicemente codificare la soluzione del codice gestito.
  2. Se prendi un programma in una lingua e lo traduci direttamente in un'altra, quanto peggiorerà? Probabilmente molto, per due lingue qualsiasi . La maggior parte delle lingue richiede ottimizzazioni diverse e hanno trucchi diversi. La micro-prestazione spesso dipende molto dalla conoscenza di questi dettagli.
  3. Dato il tempo e le risorse limitati, quale delle due lingue produrrà un risultato migliore? Questa è la domanda più interessante, poiché mentre un linguaggio gestito può produrre codice leggermente più lento (dato un programma scritto ragionevolmente per quella lingua), quella versione verrà probabilmente eseguita prima, consentendo più tempo dedicato all'ottimizzazione.

0

Una risposta molto breve: dato un budget fisso, otterrai un'applicazione java dalle prestazioni migliori rispetto a un'applicazione C ++ (considerazioni sul ROI) Inoltre la piattaforma Java ha profiler più decenti, che ti aiuteranno a individuare i tuoi hotspot più rapidamente

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.