I moderni linguaggi OO possono competere con le prestazioni del negozio di array C ++?


40

Ho appena notato che ogni moderno linguaggio di programmazione OO con cui ho almeno una certa familiarità (che è fondamentalmente solo Java, C # e D) consente array covarianti. Cioè, una matrice di stringhe è una matrice di oggetti:

Object[] arr = new String[2];   // Java, C# and D allow this

Le matrici covariant sono un buco nel sistema di tipo statico. Rendono possibili errori di tipo che non possono essere rilevati in fase di compilazione, quindi ogni scrittura su un array deve essere verificata in fase di esecuzione:

arr[0] = "hello";        // ok
arr[1] = new Object();   // ArrayStoreException

Sembra un terribile successo se faccio molti negozi di array.

Il C ++ non ha array covarianti, quindi non è necessario effettuare un controllo di runtime, il che significa che non vi è alcuna penalità di prestazione.

Esistono analisi per ridurre il numero di controlli di runtime necessari? Ad esempio, se dico:

arr[1] = arr[0];

si potrebbe sostenere che il negozio non può assolutamente fallire. Sono sicuro che ci sono molte altre possibili ottimizzazioni a cui non ho pensato.

I compilatori moderni eseguono effettivamente questo tipo di ottimizzazioni o devo convivere con il fatto che, ad esempio, un Quicksort esegue sempre O (n log n) controlli di runtime non necessari?

I moderni linguaggi OO possono evitare il sovraccarico creato supportando gli array di co-variante?


7
Sono confuso perché stai suggerendo che C ++ è più veloce di altre lingue in una funzione che C ++ non supporta nemmeno.

17
@eco: C ++ è più veloce con l'accesso agli array perché non supporta array co-varianti. Fred vuole sapere se è possibile che i "moderni linguaggi OO" evitino il sovraccarico degli array di co-variante per avvicinarsi alle velocità del C ++.
Mooing Duck,

13
"Il codice che compila ma genera eccezioni in fase di esecuzione è una cattiva notizia"

4
@Jesse E milioni di persone scrivono codice affidabile e altamente scalabile in linguaggi dinamici. Imo: se non scrivi casi di test per il tuo codice non mi importa se ci sono errori del compilatore o meno, non mi fiderò comunque.
Voo,

7
@Jesse E quando altrimenti ti aspetteresti eccezioni se non in fase di esecuzione? Il problema non è il codice che genera eccezioni in fase di esecuzione - ci sono molti casi in cui ciò ha un buon senso - il problema è il codice che è garantito essere errato che non viene catturato staticamente dal compilatore ma si traduce invece in un'eccezione a runtime.
Jonathan M Davis,

Risposte:


33

D non ha array covarianti. Li ha consentiti prima della versione più recente ( dmd 2.057 ), ma quel bug è stato corretto.

Una matrice in D è effettivamente solo una struttura con un puntatore e una lunghezza:

struct A(T)
{
    T* ptr;
    size_t length;
}

Il controllo dei limiti viene eseguito normalmente durante l'indicizzazione di un array, ma viene rimosso durante la compilazione -release. Quindi, in modalità di rilascio, non c'è una reale differenza di prestazioni tra gli array in C / C ++ e quelli in D.


Non sembra - d-programming-language.org/arrays.html dice "array A statica T[dim]può essere convertito in modo implicito una delle seguenti opzioni: ... U[]... Un array dinamico T[]può essere convertito in modo implicito una delle seguenti opzioni: U[]. .. dov'è Uuna classe base di T".
Ben Voigt,

2
@BenVoigt Quindi i documenti online devono essere aggiornati. Purtroppo non sono sempre aggiornati al 100%. La conversione funzionerà const U[], poiché da allora non è possibile assegnare il tipo sbagliato agli elementi dell'array, ma T[]sicuramente non viene convertito U[]finché U[]è mutabile. Consentire array covarianti come prima era un grave difetto di progettazione che ora è stato corretto.
Jonathan M Davis,

@JonathanMDavis: gli array covariant sono icky, ma funzionano bene per il particolare scenario di codice che scriverà solo su elementi di un array che sono stati letti dallo stesso array . In che modo D consentirebbe a uno di scrivere un metodo in grado di ordinare array di tipo arbitrario?
supercat

@supercat Se vuoi scrivere una funzione che ordina tipi arbitrari, allora templatizzala. ad es. dlang.org/phobos/std_algorithm.html#sort
Jonathan M Davis

21

Sì, un'ottimizzazione cruciale è questa:

sealed class Foo
{
}

In C #, questa classe non può essere un supertipo per nessun tipo, quindi puoi evitare il controllo per una matrice di tipo Foo.

E alla seconda domanda, negli array co-variante F # non sono consentiti (ma suppongo che il controllo rimarrà nel CLR a meno che non venga trovato inutile nelle ottimizzazioni in fase di esecuzione)

let a = [| "st" |]
let b : System.Object[] = a // Fails

https://stackoverflow.com/questions/7339013/array-covariance-in-f

Un problema in qualche modo correlato è il controllo del limite di array. Questa potrebbe essere una lettura interessante (ma vecchia) sulle ottimizzazioni fatte nel CLR (la covarianza è anche menzionata 1 posto): http://blogs.msdn.com/b/clrcodegeneration/archive/2009/08/08/13/array-bounds -check-eliminazione-in-the-clr.aspx


2
Scala impedisce anche questo costrutto: val a = Array("st"); val b: Array[Any] = aè illegale. (Tuttavia, gli array in Scala sono ... magia speciale ... a causa della JVM sottostante usata.)
pst

13

Risposta Java:

Presumo che tu non abbia effettivamente confrontato il codice, vero? In generale, il 90% di tutti i cast dinamici in Java sono gratuiti perché la JIT li può eludere (Quicksort dovrebbe essere un buon esempio per questo) e il resto è una ld/cmp/brsequenza assolutamente prevedibile (in caso contrario, beh, perché diavolo è il tuo lancio di codice tutte quelle eccezioni al cast dinamico?).

Effettuiamo il caricamento molto prima del confronto effettivo, il ramo è previsto correttamente nel 99,9999% (statistica composta!) Di tutti i casi, quindi non fermiamo la pipeline (supponendo che non colpiamo la memoria con il carico, se non bene sarà evidente, ma il carico è comunque necessario). Quindi il costo è di 1 ciclo di clock SE il JIT non può assolutamente evitare il controllo.

Qualche sovraccarico? Certo, ma dubito che lo noterai mai ..


Per aiutare a supportare la mia risposta, consultare questo post sul blog di Dr. Cliff che discute delle prestazioni Java vs.


10

D non consente matrici covarianti.

void main()
{
    class Foo {}
    Object[] a = new Foo[10];
}  

/* Error: cannot implicitly convert expression (new Foo[](10LU)) of type Foo[]
to Object[] */

Come dici tu, sarebbe un buco nel sistema di tipi per consentire questo.

Puoi essere perdonato per l'errore, poiché questo bug è stato appena corretto nell'ultimo DMD, rilasciato il 13 dicembre.

L'accesso alla matrice in D è altrettanto rapido che in C o C ++.


Secondo d-programming-language.org/arrays.html "Un array statico T [dim] può essere convertito implicitamente in uno dei seguenti: ... U [] ... Un array dinamico T [] può essere convertito implicitamente a uno dei seguenti elementi: U [] ... dove U è una classe base di T. "
Ben Voigt,

1
@BenVoigt: documenti obsoleti.
BCS,

1
@BenVoigt: ho creato una richiesta pull per aggiornare la documentazione. Spero che questo sarà risolto presto. Grazie per segnalarlo.
Peter Alexander,

5

Dal test ho fatto su un laptop economico, la differenza tra l'utilizzo int[] e Integer[]è di circa 1,0 ns. È probabile che la differenza sia dovuta al controllo extra per il tipo.

Generalmente gli oggetti vengono utilizzati solo per la logica di livello superiore quando non tutti i ns contano. Se hai bisogno di salvare ogni ns, eviterei di usare costrutti di livello superiore come Oggetti. Le assegnazioni da sole sono probabilmente fattori molto piccoli in qualsiasi programma reale. ad es. la creazione di un nuovo oggetto sulla stessa macchina è di 5 ns.

Chiamate per confrontare È probabile che siano molto più costosi, specialmente se si utilizza un oggetto complesso come String.


2

Hai chiesto informazioni su altre lingue OO moderne? Bene, Delphi evita completamente questo problemastring un primitivo, non un oggetto. Quindi un array di stringhe è esattamente un array di stringhe e nient'altro, e qualsiasi operazione su di esse è più veloce di quanto può essere il codice nativo, senza controllo del tipo di overhead.

Tuttavia, gli array di stringhe non vengono utilizzati molto spesso; I programmatori Delphi tendono a favorire la TStringListclasse. Per un minimo di sovraccarico fornisce una serie di metodi di gruppo di stringhe che sono utili in così tante situazioni che la classe è stata paragonata a un coltellino svizzero. Quindi è idiomatico utilizzare un oggetto elenco stringhe anziché un array di stringhe.

Come per altri oggetti in generale, il problema non esiste perché in Delphi, come in C ++, le matrici non sono covarianti, al fine di prevenire il tipo di tipo di buchi di sistema qui descritti.


1

o devo convivere con il fatto che, ad esempio, un Quicksort esegue sempre O (n log n) controlli di runtime non necessari?

Le prestazioni della CPU non sono monotoniche, il che significa che i programmi più lunghi possono essere più veloci di quelli più brevi (questo dipende dalla CPU, ed è vero per le architetture x86 e amd64 comuni). Quindi è possibile che un programma che esegue il controllo associato sugli array sia effettivamente più veloce del programma dedotto dal precedente rimuovendo questo controllo associato.

La ragione di questo comportamento è che il controllo associato modifica l'allineamento del codice in memoria, modificherà la frequenza degli accessi alla cache, ecc.

Quindi sì, vivi con il fatto che Quicksort esegue sempre controlli spuri O (n log n) e ottimizza dopo la profilazione.


1

Scala è un linguaggio OO che ha array invarianti, piuttosto che covarianti. Si rivolge alla JVM, quindi non ci sono prestazioni vincenti lì, ma evita un malfunzionamento comune a Java e C # che compromette la loro sicurezza del tipo per motivi di compatibilità con le versioni precedenti.

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.