Perché mai sarebbe possibile per Java essere più veloce di C ++?


80

A volte Java supera C ++ nei benchmark. Certo, a volte C ++ supera.

Vedi i seguenti link:

Ma come è possibile? Mi sorprende il fatto che interpretato dal codice potrebbe mai essere più veloce di un linguaggio compilato.

Qualcuno può spiegare per favore? Grazie!


2
Puoi dare un'occhiata a shootout.alioth.debian.org/u32/… per vedere il tipo di problemi che corrono più velocemente su java / c ++ ... Vedi lo schema dei problemi e non questi problemi specifici ...
c0da

2
Vedi Perché Java aveva la reputazione di essere lento? per molti dettagli su questo argomento.
Péter Török,

11
È contro la legge (Sezione 10.101.04.2c) produrre una VM Java che funzioni più velocemente di un binario eseguibile prodotto con C ++.
Mateen Ulhaq,

1
@muntoo Cosa diavolo vuoi dire? Sezione 10.101.04.2c di cosa?
Highland Mark,

3
@HighlandMark Non sei un membro della cerchia. Non devi sapere cosa succede all'interno del cerchio. Il cerchio è assoluto - le leggi del cerchio sostituiscono quelle della natura. Non puoi sfidare né mettere in discussione il cerchio. Sono il cerchio e il cerchio sono io.
Mateen Ulhaq,

Risposte:


108

Innanzitutto, la maggior parte delle JVM include un compilatore, quindi "interpretato dal bytecode" è in realtà piuttosto raro (almeno nel codice di riferimento - non è così raro nella vita reale, in cui il tuo codice è di solito più di alcuni loop banali che si ripetono molto spesso ).

In secondo luogo, un discreto numero dei parametri di riferimento coinvolti sembra essere piuttosto parziale (sia per intento che per incompetenza, non posso davvero dirlo). Solo per esempio, anni fa ho esaminato alcuni dei codici sorgente collegati da uno dei collegamenti che hai pubblicato. Aveva un codice come questo:

  init0 = (int*)calloc(max_x,sizeof(int));
  init1 = (int*)calloc(max_x,sizeof(int));
  init2 = (int*)calloc(max_x,sizeof(int));
  for (x=0; x<max_x; x++) {
    init2[x] = 0;
    init1[x] = 0;
    init0[x] = 0;
  }

Dato che callocfornisce memoria che è già azzerata, usare il forloop per azzerarlo di nuovo è ovviamente inutile. Questo è stato seguito (se la memoria serve) riempiendo comunque la memoria di altri dati (e nessuna dipendenza dal fatto che fosse azzerato), quindi tutto l'azzeramento era completamente inutile comunque. Sostituire il codice sopra con un semplice malloc(come qualsiasi persona sana di mente avrebbe usato per iniziare) ha migliorato la velocità della versione C ++ abbastanza da battere la versione Java (con un margine abbastanza ampio, se la memoria serve).

Considera (per un altro esempio) il methcallbenchmark utilizzato nel post di blog nel tuo ultimo link. Nonostante il nome (e il modo in cui le cose potrebbero persino apparire), la versione C ++ di questo non sta davvero misurando molto sull'overhead delle chiamate di metodo. La parte del codice che risulta essere critica è nella classe Toggle:

class Toggle {
public:
    Toggle(bool start_state) : state(start_state) { }
    virtual ~Toggle() {  }
    bool value() {
        return(state);
    }
    virtual Toggle& activate() {
        state = !state;
        return(*this);
    }
    bool state;
};

La parte critica risulta essere la state = !state;. Considera cosa succede quando cambiamo il codice per codificare lo stato come un intanziché come bool:

class Toggle {
    enum names{ bfalse = -1, btrue = 1};
    const static names values[2];
    int state;

public:
    Toggle(bool start_state) : state(values[start_state]) 
    { }
    virtual ~Toggle() {  }
    bool value() {  return state==btrue;    }

    virtual Toggle& activate() {
        state = -state;
        return(*this);
    }
};

Questa piccola modifica migliora la velocità complessiva di circa un margine di 5: 1 . Anche se il benchmark era destinato a misurare il tempo di chiamata del metodo, in realtà gran parte di ciò che stava misurando era il tempo di conversione tra inte bool. Concordo certamente sul fatto che l'inefficienza mostrata dall'originale sia sfortunata, ma dato quanto raramente sembra sorgere nel codice reale e la facilità con cui può essere risolto quando / se si presenta, ho difficoltà a pensare ne significa molto.

Nel caso in cui qualcuno decida di rieseguire i benchmark coinvolti, dovrei anche aggiungere che c'è una modifica quasi altrettanto banale alla versione Java che produce (o almeno una volta prodotta - Non ho rieseguito i test con un recente JVM per confermare che lo fanno ancora) un miglioramento abbastanza sostanziale anche nella versione Java. La versione Java ha un NthToggle :: activ () che assomiglia a questo:

public Toggle activate() {
this.counter += 1;
if (this.counter >= this.count_max) {
    this.state = !this.state;
    this.counter = 0;
}
return(this);
}

Modificarlo per chiamare la funzione base invece di manipolarlo this.statedirettamente fornisce un sostanziale miglioramento della velocità (anche se non abbastanza per stare al passo con la versione C ++ modificata).

Quindi, ciò con cui finiamo è una falsa ipotesi sui codici byte interpretati rispetto ad alcuni dei peggiori benchmark (che abbia mai visto). Nessuno dei due sta dando un risultato significativo.

La mia esperienza personale è che con programmatori altrettanto esperti che prestano uguale attenzione all'ottimizzazione, C ++ batterà Java più spesso, ma (almeno tra questi due), il linguaggio raramente farà la stessa differenza dei programmatori e del design. I benchmark citati ci dicono di più sulla (in) competenza / (dis) onestà dei loro autori di quanto non facciano sulle lingue che pretendono di benchmark.

[Modifica: Come suggerito in un punto sopra ma mai dichiarato così direttamente come probabilmente avrei dovuto, i risultati che sto citando sono quelli che ho ottenuto quando ho provato questo ~ 5 anni fa, usando implementazioni C ++ e Java che erano attuali a quel tempo . Non ho rieseguito i test con le attuali implementazioni. Uno sguardo, tuttavia, indica che il codice non è stato corretto, quindi tutto ciò che sarebbe cambiato sarebbe la capacità del compilatore di coprire i problemi nel codice.]

Se ignoriamo gli esempi Java, tuttavia, è effettivamente possibile che il codice interpretato venga eseguito più velocemente del codice compilato (sebbene difficile e in qualche modo insolito).

Il solito modo in cui ciò accade è che il codice che viene interpretato è molto più compatto del codice macchina o è in esecuzione su una CPU che ha una cache di dati più grande della cache di codice.

In tal caso, un piccolo interprete (ad esempio l'interprete interno di un'implementazione Forth) potrebbe essere in grado di adattarsi interamente alla cache del codice e il programma che sta interpretando si adatta interamente alla cache dei dati. La cache è in genere più veloce della memoria principale di un fattore di almeno 10 e spesso molto di più (un fattore di 100 non è più particolarmente raro).

Quindi, se la cache è più veloce della memoria principale di un fattore N e ci vogliono meno di N istruzioni sul codice macchina per implementare ogni codice byte, il codice byte dovrebbe vincere (sto semplificando, ma penso che l'idea generale dovrebbe ancora essere evidente).


26
+1, ack completo. Soprattutto "il linguaggio raramente farà la stessa differenza dei programmatori e del design" - spesso inciampare in problemi in cui è possibile ottimizzare l'algoritmo, ad esempio migliorare big-O che darà una spinta molto maggiore rispetto al miglior compilatore.
Schnaader,

1
"Nel caso in cui qualcuno decida di rieseguire i parametri di riferimento coinvolti ..." NON FARE! Nel 2005 quei vecchi compiti erano stati scartati e sostituiti dai compiti ora mostrati nel gioco dei benchmark. Se qualcuno desidera rieseguire alcuni programmi, esegui nuovamente i programmi correnti per le attività correnti mostrate nella home page del gioco di benchmark shootout.alioth.debian.org
igouy,

@igouy: alcune persone potrebbero voler semplicemente confermare / negare i risultati dai parametri di riferimento che hanno eseguito, con il minimo di correzioni necessarie almeno per dare loro un rapporto minimo con la realtà. Allo stesso tempo, hai fondamentalmente ragione: i benchmark in questione sono così pessimi che correggere gli errori più ovvi non sarà di grande aiuto.
Jerry Coffin,

Ed è per questo che, nel 2005, sono stati scartati e sostituiti dai compiti ora mostrati nel gioco dei benchmark. Le persone che non conoscono meglio rieseguire quei vecchi programmi.
igouy,

13
+1 Non mi piacciono le persone che codificano C ++ in stile C o Java e quindi affermano che Java è superiore. disclaimer: non chiamo nessun linguaggio superiore, ma scrivere codice C ++ scadente in uno stile che potrebbe essere perfettamente adatto a un'altra lingua non rende entrambe le lingue comparabili.
Christian Rau,

112

Il C / C ++ arrotolato a mano fatto da un esperto con un tempo illimitato sarà almeno altrettanto veloce o più veloce di Java. Alla fine, lo stesso Java è scritto in C / C ++, quindi puoi ovviamente fare tutto ciò che Java fa se sei disposto a fare abbastanza sforzo ingegneristico.

In pratica, tuttavia, Java spesso viene eseguito molto velocemente per i seguenti motivi:

  • Compilazione JIT - sebbene le classi Java siano memorizzate come bytecode, questo è (di solito) compilato in codice nativo dal compilatore JIT all'avvio del programma. Una volta compilato, si tratta di puro codice nativo, quindi teoricamente ci si può aspettare che esegua altrettanto bene come compilato C / C ++ una volta che il programma è stato eseguito per un tempo sufficientemente lungo (cioè dopo che è stata eseguita tutta la compilazione JIT)
  • La garbage collection in Java è estremamente veloce ed efficiente: l'Hotspot GC è probabilmente la migliore implementazione di GC a tutto tondo al mondo. È il risultato di molti anni di lavoro di esperti da parte di Sun e di altre società. Praticamente qualsiasi sistema di gestione della memoria complesso che si inserisce in C / C ++ sarà peggio. Ovviamente puoi scrivere schemi di gestione della memoria di base piuttosto veloci / leggeri in C / C ++, ma non saranno così versatili come un sistema GC completo. Poiché la maggior parte dei sistemi moderni richiede una complessa gestione della memoria, Java ha quindi un grande vantaggio per le situazioni del mondo reale.
  • Migliore targeting per piattaforma - ritardando la compilazione all'avvio dell'applicazione (compilazione JIT ecc.) Il compilatore Java può trarre vantaggio dal fatto di conoscere l' esatto processore su cui sta eseguendo. Ciò può consentire alcune ottimizzazioni molto vantaggiose che non si sarebbe in grado di fare nel codice C / C ++ precompilato che deve essere indirizzato a un set di istruzioni del processore "minimo comune denominatore".
  • Statistiche di runtime : poiché la compilazione JIT viene eseguita in fase di runtime, è in grado di raccogliere statistiche durante l'esecuzione del programma che consentono migliori ottimizzazioni (ad esempio, conoscendo la probabilità che venga preso un determinato ramo). Ciò può consentire ai compilatori JIT Java di produrre un codice migliore rispetto ai compilatori C / C ++ (che devono "indovinare" in anticipo il ramo più probabile, un presupposto che spesso può essere errato).
  • Librerie molto buone : il runtime Java contiene una serie di librerie scritte molto bene con buone prestazioni (specialmente per applicazioni lato server). Spesso questi sono meglio di quanto tu possa scrivere o ottenere facilmente per C / C ++.

Allo stesso tempo, C / C ++ presenta anche alcuni vantaggi:

  • Più tempo per eseguire ottimizzazioni avanzate : la compilazione C / C ++ viene eseguita una volta e può quindi impiegare molto tempo a eseguire ottimizzazioni avanzate se la si configura per farlo. Non esiste alcun motivo teorico per cui Java non possa fare lo stesso, ma in pratica si desidera che Java compili il codice JIT in modo relativamente rapido, quindi il compilatore JIT tende a concentrarsi su ottimizzazioni "più semplici".
  • Istruzioni che non sono espresse nel bytecode - mentre il bytecode Java è completamente generico, ci sono ancora alcune cose che puoi fare a basso livello che non puoi fare nel bytecode (l'aritmetica del puntatore non selezionata è un buon esempio!). Usando (ab) questo tipo di trucchi puoi ottenere alcuni vantaggi prestazionali
  • Meno vincoli di "sicurezza" : Java fa un lavoro extra per garantire che i programmi siano sicuri e affidabili. Esempi sono i controlli dei limiti sugli array, alcune garanzie di concorrenza, i controlli dei puntatori null, la sicurezza dei tipi sui cast, ecc. Evitando questi in C / C ++ è possibile ottenere alcuni miglioramenti delle prestazioni (anche se probabilmente questa può essere una cattiva idea!)

Complessivamente:

  • Java e C / C ++ possono raggiungere velocità simili
  • C / C ++ probabilmente ha un leggero vantaggio in circostanze estreme (non sorprende che gli sviluppatori di giochi AAA lo preferiscano ancora, per esempio)
  • In pratica dipenderà da come i diversi fattori sopra elencati si bilanciano per la tua specifica applicazione.

9
Annuncio "più tempo per le ottimizzazioni in C ++": questa è una delle modifiche che fa la VM Oracle quando si sceglie la VM Server: accetta un costo di avvio più elevato per consentire prestazioni più elevate nel lungo periodo. La VM client, tuttavia, è ottimizzata per un tempo di avvio ottimale. Quindi questa distinzione esiste anche all'interno di Java.
Joachim Sauer,

8
-1: un compilatore C ++ può richiedere molto più tempo (ore, letteralmente, per una libreria di grandi dimensioni) per creare un binario molto ottimizzato. Il compilatore JIT Java non può richiedere così tanto tempo, nemmeno la versione "server". Dubito seriamente che il compilatore JIT Java sarebbe in grado di eseguire l'ottimizzazione dell'intero programma come fa il compilatore MS C ++.
quant_dev,

20
@quant_dev: certo, ma non è esattamente quello che ho detto nella mia risposta come un vantaggio C ++ (più tempo per l'ottimizzazione avanzata)? Quindi perché il -1?
Mikera,

13
La garbage collection non è un vantaggio di velocità per Java. È solo un vantaggio di velocità se sei un programmatore C ++ che non sa cosa stai facendo. Se tutto ciò che stai controllando è la velocità che puoi allocare, allora sì, il garbage collector vincerà. Tuttavia, le prestazioni complessive del programma possono comunque essere eseguite meglio gestendo manualmente la memoria.
Billy ONeal,

4
... Ma con C ++, si potrebbe sempre teoricamente mettere un "livello simile a JIT" che esegue ottimizzazioni di rami simili in fase di esecuzione, mantenendo la velocità pura di un programma C ++. (Teoricamente. :()
Mateen Ulhaq,

19

Il runtime Java non sta interpretando il bytecode. Piuttosto, utilizza ciò che si chiama Just In Time Compilation . Fondamentalmente, mentre il programma viene eseguito, prende bytecode e lo converte in codice nativo ottimizzato per la CPU particolare.


In pratica si. In linea di principio, dipende: le prime macchine virtuali Java utilizzate dagli interpreti di codice e probabilmente si possono ancora trovare VM che interpretano il codice se si guarda abbastanza duramente.
Steve314,

10
@ Steve314: ma le VM puramente interpretanti non saranno quelle che supereranno il C ++, quindi non sono realmente rilevanti per questa domanda.
Joachim Sauer,

Il compilatore JIT può anche ottimizzare in modo dinamico per l'uso specifico del codice, cosa impossibile con il codice compilato staticamente.
Starblue,

2
@starblue, beh, è ​​in qualche modo possibile con una compilazione statica - vedi l'ottimizzazione guidata dal profilo.
SK-logic,

19

A parità di condizioni, potresti dire: no, Java non dovrebbe mai essere più veloce . È sempre possibile implementare Java in C ++ da zero e quindi ottenere almeno altrettanto buone prestazioni. In pratica, tuttavia:

  • JIT compila il codice sul computer dell'utente finale, consentendogli di ottimizzare per la CPU esatta in esecuzione. Sebbene ci sia un sovraccarico qui per la compilazione, potrebbe anche pagare per le app intensive. Spesso i programmi di vita reale non vengono compilati per la CPU in uso.
  • Il compilatore Java potrebbe essere meglio nell'ottimizzare automaticamente le cose rispetto a un compilatore C ++. O forse no, ma nel mondo reale le cose non sono sempre perfette.
  • Il comportamento delle prestazioni può variare a causa di altri fattori, come la garbage collection. In C ++, in genere si chiama immediatamente il distruttore quando fatto con un oggetto. In Java, rilasci semplicemente il riferimento, ritardando l'effettiva distruzione. Questo è un altro esempio di differenza che non è né qui né lì, in termini di prestazioni. Certo, puoi sostenere che potresti implementare GC in C ++ ed essere fatto con esso, ma la realtà è che poche persone lo fanno / vogliono / possono.

A parte questo, questo mi ricorda il dibattito su C negli anni '80 / '90. Tutti si chiedevano "C può mai essere veloce come il montaggio?". Fondamentalmente, la risposta è stata: no sulla carta, ma in realtà il compilatore C ha creato un codice più efficiente del 90% dei programmatori di assemblaggio (beh, una volta maturato un po ').


2
Per quanto riguarda GC, non è solo che GC può ritardare la distruzione di oggetti (che non dovrebbe importare a lungo termine); il fatto è che con i moderni GC, l'allocazione / deallocazione di oggetti di breve durata è estremamente economica in Java rispetto al C ++.
Péter Török,

@PéterTörök sì, hai ragione, buon punto.
Daniel B,

9
@ PéterTörök Ma in C ++, gli oggetti di breve durata vengono spesso messi in pila, che a sua volta è molto più veloce di qualsiasi heap GC-ed è in grado di utilizzare Java.
quant_dev,

@quant_dev, hai dimenticato un altro significativo effetto GC: la compactificazione. Quindi non sarei così sicuro da quale parte sia più veloce.
SK-logic,

3
@DonalFellows Cosa ti fa pensare che devo preoccuparmi della gestione della memoria in C ++? Il più delle volte no. Ci sono modelli semplici che devi applicare, che sono diversi da Java, ma il gioco è fatto.
quant_dev,

10

Ma l'allocazione è solo metà della gestione della memoria - la deallocazione è l'altra metà. Si scopre che per la maggior parte degli oggetti, il costo diretto della raccolta dei rifiuti è - zero. Questo perché un raccoglitore di copie non ha bisogno di visitare o copiare oggetti morti, ma solo quelli vivi. Quindi gli oggetti che diventano spazzatura poco dopo l'allocazione non contribuiscono al carico di lavoro per il ciclo di raccolta.

...

Le JVM sono sorprendentemente brave a capire cose che eravamo soliti presumere che solo lo sviluppatore potesse sapere. Consentendo alla JVM di scegliere tra allocazione di stack e allocazione di heap caso per caso, possiamo ottenere i vantaggi in termini di prestazioni dell'allocazione di stack senza far agonizzare il programmatore se allocare sullo stack o sull'heap.

http://www.ibm.com/developerworks/java/library/j-jtp09275/index.html


Questa è solo una piccola parte dell'intero quadro, ma comunque abbastanza rilevante.
Joachim Sauer,

2
Mi piace come sia la sostanza di questo: java è per i noob, fidati del GC magico, lo sa meglio.
Morg.

1
@Morg: Oppure puoi leggerlo in questo modo: Java è per le persone a cui piace fare le cose invece di perdere tempo con la manipolazione dei bit e la gestione manuale della memoria.
Landei,

4
@Landei Penso che il tuo commento avrebbe molta più credibilità se qualsiasi base di codice decente e ampiamente usata di lunga durata fosse stata scritta in Java. Nel mio mondo, i veri sistemi operativi sono scritti in C, postgreSQL è scritto in C, così come gli strumenti più importanti che sarebbe davvero un problema riscrivere. Java era (e questa è anche la versione ufficiale) per consentire alle persone meno qualificate di programmare in branchi e tuttavia raggiungere risultati tangibili.
Morg.

1
@Morg Trovo molto strano come sembri concentrarti solo sui sistemi operativi. Questo semplicemente non può essere una buona misura, per diversi motivi. In primo luogo, i requisiti dei sistemi operativi sono sostanzialmente diversi dalla maggior parte degli altri software, in secondo luogo si ha il principio del pollice Panda (chi vuole riscrivere un sistema operativo completo in un'altra lingua, chi vuole scrivere il proprio sistema operativo se ci sono alternative funzionanti e persino gratuite?) e il terzo altro software utilizza le funzionalità del sistema operativo, quindi non è necessario scrivere mai un driver del disco, un task manager, ecc. Se non riesci a fornire argomenti migliori (non basati interamente su sistemi operativi) sembri un odiatore.
Landei,

5

Mentre un programma Java completamente ottimizzato raramente batterà un programma C ++ completamente ottimizzato, differenze in cose come la gestione della memoria possono rendere molti algoritmi implementati idiomaticamente in Java più velocemente degli stessi algoritmi implementati idiomaticamente in C ++.

Come sottolineato da @Jerry Coffin, ci sono molti casi in cui semplici modifiche possono rendere il codice molto più veloce - ma spesso possono essere necessari ritocchi troppo sporchi in una lingua o nell'altra per rendere utile il miglioramento delle prestazioni. Questo è probabilmente quello che vedresti in un buon benchmark che mostra che Java sta facendo meglio del C ++.

Inoltre, anche se di solito non è poi così significativo, ci sono alcune ottimizzazioni delle prestazioni che un linguaggio JIT come Java può fare che C ++ non può fare. Il runtime Java può includere miglioramenti dopo che il codice è stato compilato, il che significa che JIT può potenzialmente produrre codice ottimizzato per sfruttare le nuove (o almeno diverse) funzionalità della CPU. Per questo motivo, un binario Java di 10 anni potrebbe potenzialmente sovraperformare un binario C ++ di 10 anni.

Infine, la sicurezza completa del tipo nel quadro più ampio può, in casi molto rari, offrire miglioramenti estremi delle prestazioni. La singolarità , un sistema operativo sperimentale scritto quasi interamente in un linguaggio basato su C #, ha comunicazioni interprocesso e multitasking molto più veloci a causa del fatto che non sono necessari limiti di processo hardware o costosi switch di contesto.


5

Postato da Tim Holloway su JavaRanch:

Ecco un esempio primitivo: indietro quando le macchine operavano in cicli determinati matematicamente, un'istruzione di ramo in genere aveva 2 tempi diversi. Uno per quando il ramo è stato preso, uno per quando il ramo non è stato preso. Di solito, il caso no-branch era più veloce. Ovviamente, ciò significava che era possibile ottimizzare la logica in base alla conoscenza di quale caso fosse più comune (soggetto al vincolo che ciò che "sappiamo" non è sempre quello che è effettivamente il caso).

La ricompilazione di JIT fa un ulteriore passo avanti. Monitora l'effettivo utilizzo in tempo reale e inverte la logica in base al caso più comune. E capovolgi di nuovo se il carico di lavoro cambia. Il codice compilato staticamente non può farlo. È così che a volte Java può superare il codice assembly / C / C ++ ottimizzato a mano.

Fonte: http://www.coderanch.com/t/547458/Performance/java/Ahead-Time-vs-Just-time


3
E ancora una volta, questo è sbagliato / incompleto. I compilatori statici con ottimizzazione guidata dal profilo possono riconoscerlo.
Konrad Rudolph,

2
Konrad, i compilatori statici possono capovolgere la logica in base al carico di lavoro corrente? A quanto ho capito, i compilatori statici generano il codice una volta e rimangono gli stessi per sempre.
Thiago Negri,

2
Carico di lavoro attuale, n. Ma carico di lavoro tipico . L'ottimizzazione guidata dal profilo analizza il modo in cui il programma viene eseguito con un carico tipico e ottimizza gli hot-spot di conseguenza, proprio come fa JIT HotSpot.
Konrad Rudolph,

4

Questo perché il passaggio finale che genera il codice macchina avviene in modo trasparente all'interno della JVM durante l'esecuzione del programma Java, anziché esplicito durante la creazione del proram C ++.

Dovresti considerare il fatto che le moderne JVM impiegano molto tempo a compilare il codice byte al volo in codice macchina nativo per renderlo il più veloce possibile. Ciò consente a JVM di eseguire tutti i tipi di trucchi del compilatore che possono essere ancora migliori conoscendo i dati di profilatura del programma in esecuzione.

Solo una cosa come incorporare automaticamente un getter, in modo che un JUMP-RETURN non sia necessario per ottenere un valore, accelera le cose.

Tuttavia, la cosa che ha davvero permesso programmi veloci è meglio ripulire in seguito. Il meccanismo di garbage collection in Java è più veloce del manuale privo di malloc in C. Molte implementazioni moderne prive di malloc usano un garbage collector sottostante.


Si noti che questa roba incorporata rende l'avvio della JVM più grande e più lento fino a quando il codice migliore non ha una possibilità di recuperare.

1
"Molte implementazioni moderne prive di malloc utilizzano un bidone della spazzatura sottostante." Veramente? Mi piacerebbe saperne di più; Hai qualche referenza?
Sean McMillan,

Grazie. Stavo cercando di trovare un modo per dire che la JVM non contiene più semplicemente un compilatore just in time che si compila in codice eseguibile, ma un compilatore hot spot che profila il codice in esecuzione e ottimizza ulteriormente di conseguenza. Io un compilatore di una volta come il C ++ fa fatica a eguagliarlo.
Highland Mark,

@SeanMcMillan, ho visto un'analisi qualche tempo fa sulle prestazioni delle implementazioni prive di malloc in cui è stato menzionato che quello più veloce ha usato un bidone della spazzatura sotto. Non ricordo dove l'ho letto.

Era il GC conservatore BDW?
Demi

4

Risposta breve - non lo è. Lascia perdere, l'argomento è vecchio quanto il fuoco o la ruota. Java o .NET non sono e non saranno più veloci di C / C ++. È abbastanza veloce per la maggior parte delle attività in cui non è necessario pensare all'ottimizzazione. Come i moduli e l'elaborazione SQL, ma è lì che finisce.

Per i benchmark o le piccole app scritte da sviluppatori incompetenti sì, il risultato finale sarà che Java / .NET sarà probabilmente vicino e forse anche più veloce.

In realtà, cose semplici come l'allocazione di memoria nello stack o semplicemente l'utilizzo di memzones uccideranno semplicemente Java / .NET sul posto.

Il mondo della spazzatura sta usando una specie di memzone con tutta la contabilità. Aggiungi memzone a C e C sarà più veloce proprio lì sul posto. Soprattutto per quei benchmark Java vs. C "codice ad alte prestazioni", che vanno così:

for(...)
{
alloc_memory//Allocating heap in a loop is verrry good, in't it?
zero_memory//Extra zeroing, we really need it in our performance code
do_stuff//something like memory[i]++
realloc//This is lovely speedup
strlen//loop through all memory, because storing string length is soo getting old
free//Java will do that outside out timing loop, but oh well, we're comparing apples to oranges here
}//loop 100000 times

Prova a usare variabili basate sullo stack in C / C ++ (o posizionamento nuovo), si traducono in sub esp, 0xff, è una singola istruzione x86, battila con Java - non puoi ...

Il più delle volte vedo quei banchi in cui vengono confrontati Java contro C ++ mi fa andare come, wth? Strategie di allocazione della memoria errate, contenitori auto-crescenti senza riserve, molteplici novità. Questo non è nemmeno vicino al codice C / C ++ orientato alle prestazioni.

Anche una buona lettura: https://days2011.scala-lang.org/sites/days2011/files/ws3-1-Hundt.pdf


1
Sbagliato. Totalmente sbagliato. Non sarai in grado di superare le prestazioni di un GC compattante con la gestione manuale della memoria. Il conteggio dei riferimenti ingenui non sarà mai migliore di un vero mark'n'sweep. Non appena si tratta di una complicata gestione della memoria, C ++ è un ritardo.
SK-logic,

3
@ SK-Logic: Sbagliato, con memzone o allocazione di stack NON c'è NESSUNA allocazione di memoria o deallocazione. Hai un blocco di memoria e ci scrivi. Contrassegna il blocco come libero con una variabile volatile come la protezione della concorrenza InterlockedExchange ecc., E il thread successivo scarica i suoi dati in blocco preallocato senza andare al sistema operativo per la memoria, se vede che è gratuito. Con lo stack è ancora più semplice, con la sola eccezione che non è possibile scaricare 50 MB nello stack. E la durata dell'oggetto è solo all'interno di {}.
Coder

2
@ SK-logic: i compilatori sono prima la correttezza, le prestazioni secondo. I motori di ricerca, i motori di database, i sistemi di trading in tempo reale, i giochi sono quelli che considererei critici in termini di prestazioni. E la maggior parte di questi si basano su strutture piatte. E in entrambi i casi, i compilatori sono per lo più scritti in C / C ++. Con gli allocatori personalizzati credo. Poi di nuovo, non vedo problemi con l'utilizzo di elementi ad albero o elenco su rammap. Devi solo utilizzare il posizionamento nuovo. Non c'è molta complessità in questo.
Coder

3
@ SK-logic: non è molto più veloce, ogni app .NET / Java che ho visto, si è sempre rivelata più lenta e un vero maiale. Ogni riscrittura dell'app gestita nel codice SANE C / C ++ ha prodotto un'app più pulita e leggera. Le app gestite sono sempre pesanti. Vedi VS2010 vs 2008. Stesse strutture dati, ma VS2010 è un HOG. Le app C / C ++ scritte correttamente di solito si avviano in millisecondi e non rimangono bloccate su schermate splash, consumando anche molta meno memoria. L'unico aspetto negativo è che devi programmare con l'hardware in mente, e molte persone non sanno come sia oggigiorno. Sono solo i benchmark in cui i gestori hanno una possibilità.
Coder

2
la tua prova aneddotica non conta. I benchmark corretti mostrano la vera differenza. È particolarmente strano che ti riferisca alle applicazioni della GUI, associate alle ingombranti e subottimali librerie della GUI. Inoltre, ciò che è più importante - in teoria il limite di prestazioni è molto più elevato per un GC correttamente implementato.
SK-logic,

2

La realtà è che entrambi sono semplicemente assemblatori di alto livello che fanno esattamente ciò che il programmatore dice loro, esattamente come il programmatore dice loro nell'ordine esatto in cui il programmatore dice loro. Le differenze di prestazioni sono così piccole da non essere rilevanti per tutti gli scopi pratici.

La lingua non è "lenta", il programmatore ha scritto un programma lento. Molto raramente un programma scritto nel modo migliore in una lingua supera (per qualsiasi scopo pratico) un programma che fa la stessa cosa usando il modo migliore della lingua alternativa, a meno che l'autore dello studio non sia disposto a macinare la sua particolare ascia.

Ovviamente se stai andando in un caso raro come i sistemi embedded realtime duri, la scelta della lingua può fare la differenza, ma quanto spesso è così? e di quei casi, quanto spesso la scelta corretta non è ciecamente ovvia.


2
In teoria, una VM JITting "ideale" deve sovraperformare il codice compilato staticamente, adeguando le sue ottimizzazioni alle informazioni di profilazione raccolte dinamicamente. In pratica, i compilatori JIT non sono ancora così intelligenti, ma sono almeno in grado di produrre un codice di qualità simile a quello dei loro peer statici più grandi e più lenti.
SK-logic,

2

Vedi i seguenti link ... Ma come è possibile? Mi sorprende il fatto che interpretato dal codice potrebbe mai essere più veloce di un linguaggio compilato.

  1. Questi post sul blog forniscono prove affidabili?
  2. Quei post sul blog forniscono prove definitive?
  3. Quei post sul blog forniscono anche prove di "interpretato bytecode"?

Keith Lea ti dice che ci sono "difetti evidenti" ma non fa nulla per quei "difetti evidenti". Nel 2005 quei vecchi compiti erano stati scartati e sostituiti dai compiti ora mostrati nel gioco dei benchmark .

Keith Lea ti dice che "ha preso il codice di riferimento per C ++ e Java dall'ormai obsoleto Great Computer Language Shootout ed ha eseguito i test" ma in realtà mostra solo misurazioni per 14 su 25 di quei test obsoleti .

Keith Lea ora ti dice che non stava cercando di provare nulla con il post sul blog sette anni prima, ma allora disse "Ero stanco di sentire la gente dire che Java era lento, quando so che è abbastanza veloce ..." il che suggerisce allora c'era qualcosa che stava cercando di dimostrare.

Christian Felde ti dice "Non ho creato il codice, ho solo rieseguito i test". come se ciò lo esonerasse da qualsiasi responsabilità per la sua decisione di pubblicizzare misurazioni dei compiti e dei programmi selezionati da Keith Lea.

Le misurazioni di anche solo 25 minuscoli programmi forniscono prove definitive?

Tali misurazioni sono per programmi eseguiti come "modalità mista" Java non interpretato Java - "Ricorda come funziona HotSpot". Puoi facilmente scoprire come Java esegue "interpretato dal bytecode", perché puoi forzare Java a interpretare solo il bytecode - semplicemente il tempo in cui alcuni programmi Java vengono eseguiti con e senza l'opzione -Xint.


-1

Mi diverte quanto sia pervasiva questa strana nozione di "interpretato bytecode". Avete mai sentito parlare della compilation JIT? Il tuo argomento non può essere applicato a Java.

Ma, lasciando da parte JVM, ci sono casi in cui un codice thread diretto o anche una banale interpretazione bytecode può facilmente superare un codice nativo fortemente ottimizzato. La spiegazione è abbastanza semplice: il bytecode può essere abbastanza compatto e si adatta alla tua piccola cache quando una versione di codice nativo dello stesso algoritmo finirà per avere diversi errori di cache per una singola iterazione.


La pervasività dell'interpretazione è forse dovuta a persone esperte nella loro informatica. La macchina virtuale Java è una macchina che accetta jtecode bytecode e la esegue su una macchina / non / in grado di eseguire java bytecode nativamente, senza essere in grado di scrivere un programma nativo funzionalmente equivalente. Ergo è un interprete. Puoi dare alle sue tecniche di memorizzazione nella cache qualsiasi nome ti venga in mente, JIT o altro, ma è un interprete secondo la definizione di interprete.
thiton,

@thiton, è probabile che il tuo CS-fu sia un po 'debole. JVM non fa alcun tipo di interpretazione (per hot spot) - come qualcuno che osa parlare CS, è necessario sapere che cosa significa, e come una semantica operative di un'interpretazione diversa da l'esecuzione di un codice nativo. Ma, probabilmente, semplicemente non conosci abbastanza CS per distinguere la compilazione dall'interpretazione.
SK-logic,

2
Umm, ma il bytecode, per essere eseguito, deve essere convertito in codice nativo - non è possibile alimentare il bytecode Java nella CPU. Quindi l'argomento size non è valido.
quant_dev,

@quant_dev, ovviamente - ho detto che questo caso non è assolutamente correlato a JVM. Avresti bisogno di un motore bytecode molto più semplice per far funzionare quell'effetto.
SK-logic,

-1

A parte JIT, GC e così via, il C ++ può essere reso molto, molto facilmente molto più lento di Java. Questo non comparirà nei benchmark, ma la stessa app scritta dallo sviluppatore Java e da uno sviluppatore C ++ potrebbe essere molto più veloce in Java.

  • Sovraccarico dell'operatore. Ogni operatore semplice come "+" o "=" può chiamare centinaia di righe di codice eseguendo controlli di sicurezza, operazioni su disco, registrazione, tracciamento e profilazione. E sono così facili da usare che una volta sovraccaricato gli operatori, li usi in modo naturale e abbondante senza notare come si accumula l'utilizzo.
  • Modelli. Questi non influiscono sulla velocità tanto quanto la memoria. L'uso non intenzionale di modelli porterà a generare milioni di righe di codice (alternative per il modello di base) senza che tu te ne accorga mai. Ma poi i tempi di caricamento binari, l'utilizzo della memoria, l'utilizzo degli swap - tutto ciò che agisce anche rispetto ai benchmark. E l'utilizzo della RAM passa attraverso il tetto.

Per quanto riguarda i modelli di ereditarietà avanzata, questi sono praticamente simili: C ++ ne ha alcuni che Java non ha e viceversa, ma tutti presentano anche un sovraccarico simile e significativo. Quindi nessun vantaggio speciale in C ++ nella programmazione con oggetti pesanti.

Un altro avvertimento: GC può essere più veloce o più lento rispetto alla gestione manuale delle allocazioni. Se si allocano molti piccoli oggetti, in genere nell'ambiente GC viene allocata una porzione di memoria e pezzi di essa vengono inviati secondo necessità per i nuovi oggetti. In gestito: ogni oggetto = allocazione separata richiede molto tempo. OTOH, se malloc () molta memoria alla volta e poi ne assegni solo pezzi manualmente ai tuoi oggetti, o usi poche istanze più grandi di oggetti, potresti venire molto più velocemente.


4
Non sono d'accordo con entrambi i punti. Non importa se usi operatori o metodi. Dici che prolifereranno. Assurdità: non più dei metodi; o devi chiamarli o no. E i modelli non producono altro codice che scrivere di nuovo quel particolare codice per un uso multiplo. Ci possono essere più di codice che con la spedizione di runtime (funzioni virtuali), ma anche questo, sarà irrilevante: le prestazioni delle linee di cache istruzioni conta di più in cicli stretti e qui ci sarà solo un essere utilizzato modello di istanza, quindi non c'è alcuna pressione di memoria rilevanti a causa di modelli.
Konrad Rudolph,

La solita mentalità è che i metodi sono costosi, gli operatori sono economici. Usa i metodi quando è necessario, gli operatori ogni volta che vuoi risparmiare tempo e ottimizzare. Non è una questione tecnica, ma psicologica - non è che gli operatori siano "più pesanti", sono solo molto più facili da usare e vengono utilizzati molto più frequentemente. (Inoltre puoi sovraccaricare un operatore comunemente usato in un codice preesistente, facendolo fare come l'originale, più un extra - e improvvisamente l'intero codice rallenterà in modo significativo.
SF.

Contesto che questo fatto psicologico sia reale e, anche se lo è, non hai scelta : se hai bisogno di una funzionalità, la usi, sia che sia incapsulata in un operatore o in un metodo. La psicologia è irrilevante per la tua scelta della semantica.
Konrad Rudolph,

1
Domanda a trabocchetto. Non lo indovinerei affatto, misurerei, agirei allora . Non ho mai avuto problemi con quella tattica.
Konrad Rudolph,

1
@KonradRudolph: questo è vero quando si tratta di chiarezza e facilità di scrittura del codice, rendendolo privo di bug e mantenibile. Il punto sull'efficienza dell'implementazione dell'algoritmo è ancora valido: se stai per scrivere obj.fetchFromDatabase("key")tre volte in cinque righe di codice per la stessa chiave, penserai due volte se recuperare quel valore una volta e memorizzarlo nella cache in una variabile locale. Se scrivi obj->"key"con ->l'overloading per agire come recupero del database, sei molto più incline a lasciarlo passare perché il costo dell'operazione non è evidente.
SF.

-2

In qualche modo Stack Exchange non prende gli altri miei stackpoint quindi ... nessuna risposta purtroppo ...

Comunque la seconda risposta più votata qui è piena di disinformazione secondo la mia modesta opinione.

Un'app lanciata a mano da un esperto in C / C ++ è SEMPRE molto più veloce di un'applicazione Java, punto. Non esiste "veloce come Java o più veloce". è solo più veloce, proprio per gli elementi che citi di seguito:

Compilazione JIT : ti aspetti davvero che un ottimizzatore automatico abbia l'intelligenza di un programmatore esperto e vedi il collegamento tra l'intento e il codice che la CPU eseguirà davvero ??? Inoltre, tutto il JIT che fai è tempo perso rispetto a un programma già compilato.

Raccolta dei rifiuti è uno strumento che sposta semplicemente risorse che un programmatore avrebbe dimenticato di deallocare, in modo più o meno efficiente.

Evidentemente questo può essere solo più lento di quello che farebbe un programmatore esperto (hai scelto il termine) C per gestire la sua memoria (e non ci sono perdite nelle app scritte correttamente).

Un'applicazione C ottimizzata per le prestazioni conosce la CPU su cui è in esecuzione, è stata compilata su di essa, altrimenti significa che non hai preso tutti i passaggi per le prestazioni, vero?

Statistiche di runtime Questo è al di là delle mie conoscenze, ma sospetto che un esperto in C abbia più che sufficienti conoscenze di previsione del ramo per superare ancora una volta l'ottimizzazione automatizzata -

Librerie molto buone Ci sono molte funzioni non molto ottimizzate prontamente disponibili attraverso le librerie in Java, e lo stesso vale in qualsiasi lingua, tuttavia le librerie più ottimizzate sono scritte in C, specialmente per il calcolo.

La JVM è uno strato di astrazione, che implica entrambe le cose buone, molte delle quali sono sopra, e implica anche che la soluzione complessiva è più lenta dal design.

Complessivamente:

Java non può mai raggiungere la velocità di C / C ++ a causa del modo in cui funziona in una JVM con molta protezione, funzionalità e strumenti.

Il C ++ ha un chiaro vantaggio nel software ottimizzato, sia esso per informatica o giochi, ed è normale vedere le implementazioni C ++ vincere concorsi di codifica al punto che le migliori implementazioni Java possono essere viste solo sulla seconda pagina.

In pratica il C ++ non è un giocattolo e non ti permetterà di cavartela con molti errori che la maggior parte delle lingue moderne può gestire, tuttavia essendo più semplice e meno sicura, è intrinsecamente più veloce.

E, in conclusione, vorrei dire che la maggior parte delle persone non dà due centesimi a questo proposito, che alla fine l'ottimizzazione è uno sport riservato solo a pochissimi sviluppatori fortunati e che, tranne nei casi in cui le prestazioni sono davvero una preoccupazione (vale a dire la moltiplicazione dell'hardware per 10 non ti aiuterà - o rappresenterà almeno qualche milione), la maggior parte dei gestori preferirà un'app non ottimizzata e una tonnellata di hardware.


Ancora. La garbage collection non è solo uno "strumento che dealloca". GC può compattare le tue strutture. GC può gestire i tuoi riferimenti deboli e aiutarti a bilanciare la cache in questo modo. GC multistadio rende l'allocazione dell'heap molto più economica di quella voluminosa, lenta newo malloc(). Può essere molto più veloce in generale rispetto a qualsiasi gestione manuale della memoria, poiché non sarebbe possibile spostare gli oggetti manualmente. Quindi, tutto il tuo ragionamento è chiaramente sbagliato e distorto. La tua conoscenza degli algoritmi GC e dei metodi di ottimizzazione JIT è troppo limitata.
SK-logic,

4
Questa risposta è piena di idee sbagliate su ciò che i moderni ottimizzatori possono fare. Il codice ottimizzato a mano non ha alcuna possibilità. Ma poi, C ++ ha anche un compilatore di ottimizzazione.
Konrad Rudolph,

Grazie per il tuo commento SK-logic, ma mentre lo affermi, il GC può essere molto più veloce in generale, stiamo parlando di quello che sarà il più veloce in un caso particolare, e beh sembra che la maggior parte delle persone sia d'accordo sul fatto che tutto ciò che il GC può può fare un programmatore, e ancora meglio. Ovviamente puoi riposizionare gli oggetti manualmente quando hai accesso diretto alla memoria .. lol. La mia conoscenza degli interni di JVM è sicuramente limitata e mi aspetto che Java-head mi mostri la luce, non solo mi dica schifezze casuali sul GC che è in grado di fare cose che non si possono fare manualmente (lol ... anche il GC deve utilizzare le istruzioni della CPU;)).
Morg.

Konrad, sono d'accordo sul fatto che sottovaluto ampiamente gli ottimizzatori moderni ... tuttavia trovo interessante considerare il codice ottimizzato a mano come inferiore al codice ottimizzato automaticamente. Cosa ti aspetti esattamente dal compilatore di vedere che un essere umano non può?
Morg.

1
Giusto . continua a spingere -1, che non cambierà il fatto che C ++ è più veloce di Java. Forse non so molto sui compilatori moderni, ma questo non fa alcuna differenza sul punto principale, che è corretto e contraddice la risposta più votata qui. Perché altrimenti C ++ sarebbe una priorità per nVidia sulle loro GPU per HPC. Perché altrimenti tutti i giochi dovrebbero essere scritti in C ++, perché altrimenti ogni singolo motore DB dovrebbe essere scritto in C?
Morg.

-4

Ho visto almeno due impressionanti mmo in Java, per dire che non è abbastanza veloce per i giochi è un termine improprio. Solo perché i progettisti di giochi preferiscono il C ++ più di altri linguaggi, afferma che non è semplicemente legato a Java, significa solo che i programmatori non si sono mai dilettati con altri linguaggi / paradigmi di programmazione. Qualsiasi cosa in qualsiasi linguaggio avanzato come C / C ++ o anche Java può produrre codice che potrebbe tecnicamente soddisfare o sconfiggere l'argomento speed. Tutto ciò che dice bene dipende da ciò che i programmatori sanno, con quali team lavorano di più e, soprattutto, perché usano tali strumenti. Dal momento che stiamo affrontando l'aspetto di sviluppo del gioco della programmazione, allora ci deve essere di più sull'argomento. In poche parole ' s tutto su soldi e tempo per un'azienda morta che usa strumenti che soddisfano il QA e nel mondo reale non ha alcun peso sui motivi xx per la scelta del C ++ su Java o di qualsiasi altro linguaggio. È solo una decisione di produzione in serie. Al livello più elementare degli algoritmi di elaborazione con cui tutti stiamo giocando sono quelli e gli zeri, l'argomento velocità è uno degli argomenti più stupidi mai applicati ai giochi. Se vuoi che la velocità aumenti così tanto, abbandona completamente i linguaggi di programmazione e lavora con assembly che è probabilmente il miglior vantaggio di gran lunga.


2
Questo muro di testo non sembra aggiungere nulla che non sia già stato dichiarato nelle altre risposte. Si prega di modificare la risposta per essere più leggibile, e vi prego di accertarsi che gli indirizzi di risposta non punti sollevati dalla altra risposta. Altrimenti, ti preghiamo di considerare di eliminare la tua risposta in quanto aggiunge solo rumore a questo punto.
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.