Domanda

Il modo .NET 1.0 di creare una raccolta di numeri interi (ad esempio) era:

ArrayList list = new ArrayList();
list.Add(i);          /* boxing   */
int j = (int)list[0]; /* unboxing */

Lo svantaggio nell'utilizzarlo è la mancanza di sicurezza del tipo e di prestazioni dovute al boxing e all'unboxing.

Il modo .NET 2.0 consiste nell'utilizzare i generici:

List<int> list = new List<int>();
list.Add(i);
int j = list[0];

Il prezzo del boxing (per quanto mi risulta) è la necessità di creare un oggetto nell'heap, copiare lo stack intero assegnato al nuovo oggetto e viceversa per l'unboxing.

In che modo l’uso dei generici supera questo problema?Il numero intero allocato nello stack rimane nello stack e viene puntato dall'heap (immagino che non sia così a causa di ciò che accadrà quando uscirà dall'ambito)?Sembra che ci sia ancora bisogno di copiarlo da qualche altra parte fuori dallo stack.

Cosa sta veramente succedendo?

È stato utile?

Soluzione

Quando si tratta di collezioni, farmaci generici permettono di evitare la boxe / unboxing, utilizzando gli array T[] effettivi internamente. List<T> per esempio utilizza una matrice T[] per memorizzare il contenuto.

array , naturalmente, è un tipo di riferimento ed è quindi (nella versione corrente del CLR, bla bla) memorizzati sul mucchio. Ma dal momento che si tratta di un T[] e non un object[], gli elementi della matrice possono essere memorizzati "direttamente": cioè, sono ancora sul mucchio, ma sono sul mucchio nella matrice al posto di di essere inscatolato e avere l'array contiene i riferimenti alle caselle.

Così per un List<int>, per esempio, quello che si avrebbe avuto nella matrice avrebbe "look" in questo modo:

[ 1 2 3 ]

Confronta questo ad un ArrayList, che utilizza un object[] e sarebbe quindi "look" qualcosa di simile a questo:

[ *a *b *c ]

... dove *a, ecc, sono i riferimenti a oggetti (interi boxed):

*a -> 1
*b -> 2
*c -> 3

scusi quelle illustrazioni di greggio; si spera si sa cosa voglio dire.

Altri suggerimenti

Il tuo confusione è il risultato di incomprensione quale sia il rapporto è tra la pila, mucchio, e le variabili. Ecco il modo corretto di pensare a questo proposito.

  • Una variabile è una posizione di memoria che ha un tipo.
  • La durata di una variabile può essere sia breve o lungo. Con il termine "breve" si intende "fino a quando la funzione ritorna attuali o getta" e "lungo" si intende "probabilmente più lungo di quello".
  • Se il tipo di una variabile è un tipo di riferimento, allora il contenuto della variabile è un riferimento a una posizione di archiviazione di lunga durata. Se il tipo di una variabile è un tipo di valore, allora il contenuto della variabile è un valore.

Come un dettaglio di implementazione, un magazzino che è garantito per essere di breve durata può essere allocata in pila. Una posizione di memoria che potrebbe essere lunga durata, è allocato sul mucchio. Si noti che questo non dice nulla circa "i tipi di valore sono sempre allocate sullo stack." I tipi di valore sono non sempre allocato sullo stack:

int[] x = new int[10];
x[1] = 123;

x[1] è un percorso di archiviazione. E 'vissuto a lungo; potrebbe vivere più a lungo di questo metodo. Pertanto deve essere sul mucchio. Il fatto che contiene un int è irrilevante.

È corretto dire il motivo per cui un int boxed è costoso:

  

Il prezzo di boxe è la necessità di creare un oggetto sul mucchio, copiare lo stack allocato intero al nuovo oggetto e viceversa per unboxing.

Dove si va male è quello di dire "lo stack allocato intero". Non importa dove il numero intero è stato assegnato. Ciò che conta è che il suo stoccaggio conteneva l'intero , invece di contenere un riferimento a una posizione mucchio . Il prezzo è la necessità di creare l'oggetto e fare la copia; questo è l'unico costo che è rilevante.

Quindi, perché non è una variabile generica costosa? Se si dispone di una variabile di tipo T, e T è costruito per essere Int, allora si ha una variabile di tipo int, punto. Una variabile di tipo int è una posizione di memorizzazione, e contiene un int. Sia che posizione di archiviazione è in pila o il cumulo è del tutto irrilevante . Ciò che conta è che la posizione di archiviazione contiene un int , invece di contenere un riferimento a qualcosa sul mucchio . Dal momento che la posizione di memorizzazione contiene un int, non c'è bisogno di assumere i costi di boxe e unboxing:. Allocare nuovo storage sul mucchio e copiando l'int al nuovo storage

è che ora chiaro?

Generics permette array interno della lista per essere digitato int[] invece di efficace object[], che richiederebbe la boxe.

Ecco cosa succede senza farmaci generici:

  1. Si chiama Add(1).
  2. Il 1 numero intero viene confezionato in un oggetto, che richiede un nuovo oggetto da costruire sul mucchio.
  3. Questo oggetto viene passato al ArrayList.Add().
  4. L'oggetto boxed viene insaccato in un object[].

Ci sono tre livelli di indirezione qui:. ArrayList -> object[] -> object -> int

Con farmaci generici:

  1. Si chiama Add(1).
  2. L'int 1 è passato a List<int>.Add().
  3. L'int viene insaccato in un int[].

Quindi, ci sono solo due livelli di riferimento indiretto:. List<int> -> int[] -> int

A poche altre differenze:

  • Il metodo non generico richiederà una somma di 8 o 12 byte (un puntatore, un int) per memorizzare il valore, 4/8 in un'allocazione e 4 nell'altra. E questo sarà probabilmente più a causa di allineamento e imbottitura. Il metodo generico richiede solo 4 byte di spazio nella matrice.
  • Il metodo non generico richiede l'assegnazione di un int scatolato; il metodo generico non lo fa. Questo è più veloce e riduce il tasso di abbandono GC.
  • Il metodo non generico richiede calchi ai valori estratto. Questo non è typesafe ed è un po 'più lento.

Un ArrayList gestisce solo il tipo object in modo da utilizzare questa classe richiede colata da e per object. Nel caso di tipi di valore, questo casting comporta la boxe e unboxing.

Quando si utilizza un elenco generico delle uscite compilatore codice specializzato per questo tipo di valore in modo che i valori reali sono memorizzati nella lista, piuttosto che un riferimento a oggetti che contengono i valori. Pertanto non è necessario alcun boxe.

  

Il prezzo di boxe (la mia comprensione) è la necessità di creare un oggetto sul mucchio, copiare lo stack allocato intero al nuovo oggetto e viceversa per unboxing.

Penso che si stanno assumendo che i tipi di valore sono sempre istanziati in pila. Questo non è il caso - possono essere creati sia sul mucchio, sullo stack o in registri. Per ulteriori informazioni su questo si veda l'articolo di Eric Lippert: La verità su Valore Tipi .

In .NET 1, quando Add il metodo si chiama:

  1. Lo spazio viene allocato nell'heap;viene fatto un nuovo riferimento
  2. Il contenuto del i la variabile viene copiata nel riferimento
  3. Una copia del riferimento viene inserita alla fine dell'elenco

In .NET 2:

  1. Una copia della variabile i viene passato al Add metodo
  2. Una copia di quella copia viene inserita alla fine dell'elenco

Sì, il i variabile viene ancora copiata (dopo tutto, è un tipo di valore e i tipi di valore vengono sempre copiati, anche se sono solo parametri di metodo).Ma non viene creata alcuna copia ridondante nell'heap.

Perché si sta pensando in termini di WHERE i valori \ oggetti vengono memorizzati? In C # i tipi di valore possono essere memorizzati su una pila così come mucchio a seconda di quello dei sceglie CLR.

Dove generici fanno la differenza viene WHAT è archiviato nella raccolta. In caso di ArrayList la raccolta contiene riferimenti a oggetti in scatola dove la List<int> contiene valori int stessi.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top