Qualcuno conosce la risposta e / o ha un'opinione al riguardo?
Poiché le tuple normalmente non sarebbero molto grandi, presumo che avrebbe più senso usare gli struct rispetto alle classi per questi. Che ne dici?
Qualcuno conosce la risposta e / o ha un'opinione al riguardo?
Poiché le tuple normalmente non sarebbero molto grandi, presumo che avrebbe più senso usare gli struct rispetto alle classi per questi. Che ne dici?
Risposte:
Microsoft ha creato tutti i tipi di tupla tipi di riferimento per motivi di semplicità.
Personalmente penso che sia stato un errore. Le tuple con più di 4 campi sono molto insolite e dovrebbero essere sostituite comunque con un'alternativa più tipizzata (come un tipo di record in F #), quindi solo le piccole tuple sono di interesse pratico. I miei benchmark hanno mostrato che le tuple unboxed fino a 512 byte potrebbero essere ancora più veloci delle tuple in box.
Sebbene l'efficienza della memoria sia una delle preoccupazioni, credo che il problema dominante sia l'overhead del Garbage Collector .NET. L'allocazione e la raccolta sono molto costose su .NET perché il suo garbage collector non è stato ottimizzato molto (ad esempio rispetto alla JVM). Inoltre, il .NET GC (workstation) predefinito non è stato ancora parallelizzato. Di conseguenza, i programmi paralleli che utilizzano tuple si arrestano poiché tutti i core si contendono il garbage collector condiviso, distruggendo la scalabilità. Questa non è solo la preoccupazione dominante ma, per quanto ne so, è stata completamente trascurata da Microsoft quando ha esaminato questo problema.
Un'altra preoccupazione è l'invio virtuale. I tipi di riferimento supportano i sottotipi e, pertanto, i loro membri vengono generalmente richiamati tramite invio virtuale. Al contrario, i tipi di valore non possono supportare i sottotipi, quindi il richiamo dei membri è del tutto univoco e può sempre essere eseguito come chiamata di funzione diretta. L'invio virtuale è estremamente costoso sull'hardware moderno perché la CPU non può prevedere dove andrà a finire il contatore del programma. La JVM fa di tutto per ottimizzare l'invio virtuale, ma .NET non lo fa. Tuttavia, .NET fornisce una via di fuga dall'invio virtuale sotto forma di tipi di valore. Quindi rappresentare le tuple come tipi di valore potrebbe, ancora una volta, migliorare notevolmente le prestazioni qui. Ad esempio, chiamandoGetHashCode
su una tupla 2 un milione di volte richiede 0.17s ma chiamarlo su una struttura equivalente richiede solo 0.008s, cioè il tipo di valore è 20 volte più veloce del tipo di riferimento.
Una situazione reale in cui questi problemi di prestazioni con le tuple si presentano comunemente è nell'uso delle tuple come chiavi nei dizionari. In realtà mi sono imbattuto in questo thread seguendo un collegamento dalla domanda Stack Overflow F # esegue il mio algoritmo più lentamente di Python! dove il programma F # dell'autore si è rivelato più lento del suo Python proprio perché utilizzava tuple in scatola. L'unboxing manuale utilizzando un struct
tipo scritto a mano rende il suo programma F # molte volte più veloce e più veloce di Python. Questi problemi non sarebbero mai sorti se le tuple fossero rappresentate da tipi di valore e non da tipi di riferimento per cominciare ...
Tuple<_,...,_>
tipi avrebbero potuto essere sigillati, nel qual caso non sarebbe stato necessario alcun invio virtuale nonostante fossero tipi di riferimento. Sono più curioso del motivo per cui non sono sigillati rispetto al motivo per cui sono tipi di riferimento.
Il motivo è molto probabile perché solo le tuple più piccole avrebbero senso come tipi di valore poiché avrebbero un footprint di memoria ridotto. Le tuple più grandi (cioè quelle con più proprietà) avrebbero effettivamente sofferto in termini di prestazioni poiché sarebbero più grandi di 16 byte.
Piuttosto che avere alcune tuple come tipi di valore e altre essere tipi di riferimento e costringere gli sviluppatori a sapere quali sono quali immagino che la gente di Microsoft pensasse che renderli tutti i tipi di riferimento fosse più semplice.
Ah, sospetti confermati! Vedere Creazione di tupla :
La prima decisione importante era se trattare le tuple come tipo di riferimento o valore. Poiché sono immutabili ogni volta che si desidera modificare i valori di una tupla, è necessario crearne una nuova. Se sono tipi di riferimento, significa che possono essere generati molti rifiuti se si modificano gli elementi in una tupla in un ciclo stretto. Le tuple F # erano tipi di riferimento, ma il team aveva la sensazione che avrebbero potuto realizzare un miglioramento delle prestazioni se due, e forse tre, tuple di elemento fossero invece tipi di valore. Alcuni team che avevano creato tuple interne avevano usato il valore invece dei tipi di riferimento, perché i loro scenari erano molto sensibili alla creazione di molti oggetti gestiti. Hanno scoperto che l'utilizzo di un tipo di valore dava loro prestazioni migliori. Nella nostra prima bozza della specifica della tupla, abbiamo mantenuto le tuple a due, tre e quattro elementi come tipi di valore, con il resto come tipi di riferimento. Tuttavia, durante una riunione di progettazione che includeva rappresentanti di altre lingue, è stato deciso che questo progetto "diviso" sarebbe stato fonte di confusione, a causa della semantica leggermente diversa tra i due tipi. È stato stabilito che la coerenza nel comportamento e nel design ha una priorità maggiore rispetto ai potenziali aumenti delle prestazioni. In base a questo input, abbiamo modificato il design in modo che tutte le tuple siano tipi di riferimento, sebbene abbiamo chiesto al team di F # di eseguire alcune indagini sulle prestazioni per vedere se si è verificato un aumento della velocità quando si utilizza un tipo di valore per alcune dimensioni di tuple. Aveva un buon modo per testarlo, dal momento che il suo compilatore, scritto in F #, era un buon esempio di un programma di grandi dimensioni che utilizzava tuple in una varietà di scenari. Alla fine, il team di F # ha scoperto che non ha ottenuto un miglioramento delle prestazioni quando alcune tuple erano tipi di valore anziché tipi di riferimento. Questo ci ha fatto sentire meglio riguardo alla nostra decisione di utilizzare i tipi di riferimento per la tupla.
Se i tipi .NET System.Tuple <...> fossero definiti come strutture, non sarebbero scalabili. Ad esempio, una tupla ternaria di interi lunghi scala attualmente come segue:
type Tuple3 = System.Tuple<int64, int64, int64>
type Tuple33 = System.Tuple<Tuple3, Tuple3, Tuple3>
sizeof<Tuple3> // Gets 4
sizeof<Tuple33> // Gets 4
Se la tupla ternaria fosse definita come una struttura, il risultato sarebbe il seguente (basato su un esempio di test che ho implementato):
sizeof<Tuple3> // Would get 32
sizeof<Tuple33> // Would get 104
Poiché le tuple hanno il supporto della sintassi incorporato in F # e sono usate molto spesso in questo linguaggio, le tuple "struct" metterebbero i programmatori di F # a rischio di scrivere programmi inefficienti senza nemmeno esserne consapevoli. Sarebbe così facile:
let t3 = 1L, 2L, 3L
let t33 = t3, t3, t3
A mio parere, le tuple "struct" causerebbero un'alta probabilità di creare significative inefficienze nella programmazione quotidiana. D'altra parte, le tuple di "classe" attualmente esistenti causano anche alcune inefficienze, come menzionato da @Jon. Tuttavia, penso che il prodotto della "probabilità di occorrenza" per il "danno potenziale" sarebbe molto più alto con le strutture di quanto non lo sia attualmente con le classi. Pertanto, l'attuale implementazione è il male minore.
Idealmente, ci sarebbero sia tuple "class" che tuple "struct", entrambe con supporto sintattico in F #!
Modifica (07/10/2017)
Le tuple Struct sono ora completamente supportate come segue:
ref
, o potrebbe non piacere il fatto che le cosiddette "strutture immutabili" non lo siano, specialmente quando sono inscatolate. Peccato che .net non abbia mai implementato il concetto di passaggio di parametri da parte di un enforceable const ref
, poiché in molti casi tale semantica è ciò che è veramente richiesto.
Dictionary
, ad esempio qui: stackoverflow.com/questions/5850243 /…
Per 2-tuple, puoi sempre usare KeyValuePair <TKey, TValue> dalle versioni precedenti del Common Type System. È un tipo di valore.
Un piccolo chiarimento all'articolo di Matt Ellis sarebbe che la differenza nella semantica d'uso tra i tipi di riferimento e valore è "lieve" solo quando l'immutabilità è attiva (cosa che, ovviamente, sarebbe il caso qui). Tuttavia, penso che sarebbe stato meglio nel progetto BCL non introdurre la confusione di avere Tuple incrociato con un tipo di riferimento a una certa soglia.
Non lo so, ma se hai mai usato F # Le tuple fanno parte del linguaggio. Se ho creato un .dll e restituito un tipo di tuple, sarebbe bello avere un tipo in cui inserirlo. Sospetto ora che F # fa parte del linguaggio (.Net 4) alcune modifiche a CLR sono state apportate per ospitare alcune strutture comuni in fa #
Da http://en.wikibooks.org/wiki/F_Sharp_Programming/Tuples_and_Records
let scalarMultiply (s : float) (a, b, c) = (a * s, b * s, c * s);;
val scalarMultiply : float -> float * float * float -> float * float * float
scalarMultiply 5.0 (6.0, 10.0, 20.0);;
val it : float * float * float = (30.0, 50.0, 100.0)
ValueTuple<...>
. Vedere il riferimento ai tipi di tupla C #