2011-09-12 27 views
12

La boxing converte un tipo di valore in un tipo di oggetto. O come dice MSDN, boxing è una "operazione per avvolgere la struttura all'interno di un oggetto di tipo riferimento sull'heap gestito".C# - È possibile raggruppare le scatole?

Ma se si tenta di approfondire guardando il codice IL, si vede solo la parola magica "scatola". Classe segreto

Speculare, immagino che il runtime ha una sorta di farmaci generici a base nella manica, come Box<T> con una proprietà public T Value, e la boxe un int sarebbe simile:

int i = 5; 
Box<int> box = new Box<int>; 
box.Value = 5; 

unboxing l'int sarebbe essere molto più economico: return box.Value;

Sfortunatamente, la mia applicazione server con prestazioni elevate fa un bel po 'di boxe, in particolare di decimali. Peggio ancora, queste scatole sono di breve durata, il che mi fa sospettare che io paghi due volte, una volta per istanziare la scatola e poi di nuovo per la raccolta dei rifiuti dopo che ho finito.

Se mi stavo dedicando questa memoria, considererei l'uso di un pool di oggetti qui. Ma dal momento che la creazione dell'oggetto reale è nascosta dietro una parola magica nell'IL, quali sono le mie opzioni?

Le mie domande specifiche:

  • Esiste un meccanismo esistente per indurre il runtime di prendere scatole da un pool piuttosto che instanciating?
  • Qual è il tipo di istanza creata durante il pugilato? È possibile assumere manualmente il controllo del processo di boxe, ma essere comunque compatibile con l'unboxing?

Se quest'ultima domanda sembra strano, quello che voglio dire è che ho potuto creare il mio Box<T> o DecimalBox classe, piscina, e box/Unbox manualmente. Ma non voglio dover andare a modificare i vari punti del codice che consumano il valore in scatola (aka unbox it).

+0

Avete considerato solo la riduzione delle operazioni di boxing utilizzando generici/oggetti personalizzati/ecc.? L'istruzione 'box' è intenzionalmente opaca, anche se puoi ovviamente imitarla tramite la tua classe' Box '. – dlev

+10

"la mia applicazione fa un bel po 'di boxe". Un gioco strano, l'unica mossa vincente non è quella di giocare. –

+5

"Peggio ancora, queste caselle sono di breve durata, il che mi fa sospettare pago due volte, una per instanciating la scatola e poi di nuovo per la raccolta dei rifiuti la casella dopo ho finito con esso." L'hai profilato, vero? Sai che il GC è un collo di bottiglia nella tua applicazione perché hai misurato, e non perché hai indovinato. L'unica volta in cui sono riuscito a ottenere il limite di GC è stato l'allocazione frequente di array di byte di grandi dimensioni. Se le collezioni di Gen0 sono un collo di bottiglia, dovresti riconsiderare il tuo design. – CodesInChaos

risposta

10

Speculare, immagino che il runtime ha una sorta di classe segreto generici basati nella manica

tuo speculazione è quasi giusto. Logicamente si può pensare a una scatola come ad un magico tipo Box<T> che si comporta come si descrive (con qualche altro bit di magia, ad esempio, il modo in cui la casella dei valori nullable è un po 'insolita). , il runtime non lo fa con tipi generici. La boxe esisteva in CLR v1, che era prima che i tipi generici venissero aggiunti al sistema di tipi.

la mia applicazione server con prestazioni elevate fa un bel po 'di boxe, in particolare di decimali.

Se fa male quando lo fai allora smettere di farlo. Piuttosto che cercare di rendere la boxe più economica, smettere di farlo in primo luogo. Perché stai inscatolando un decimale?

Peggio ancora, queste scatole sono di breve durata, il che mi fa sospettare pago due volte, una per instanciating la scatola e poi di nuovo per la raccolta dei rifiuti la casella dopo ho finito con esso.

breve durata è meglio di lunga durata; con oggetti di heap di breve durata si paga per ritirarli una volta e quindi sono morti. Con oggetti heap longevi si paga quel costo più e più volte mentre l'oggetto continua a sopravvivere.

Naturalmente, il costo che probabilmente si preoccupa di oggetti di breve durata non è il costo della raccolta di per sé. Piuttosto, è la pressione di raccolta ; più oggetti di breve durata allocati equivale a raccolte di rifiuti più frequenti.

Il costo di allocazione è piuttosto ridotto. Sposta un puntatore sull'heap GC, copia il decimale in quella posizione, fatto.

Se stavo alloacting questo ricordo me stesso, vorrei prendere in considerazione l'uso di un pool di oggetti qui.

destro; paghi di più il costo della raccolta dell'oggetto longevo, ma fai meno raccolte in totale perché viene prodotta meno pressione di raccolta. Questo può essere una vittoria.

Esiste un meccanismo esistente per indurre il runtime a prelevare scatole da un pool anziché installarle?

No.

Qual è il tipo di istanza creata durante il pugilato? È possibile assumere manualmente il controllo del processo di boxe, ma essere comunque compatibile con l'unboxing?

Il tipo di casella è il tipo di oggetto in scatola. Chiedilo semplicemente chiamando GetType; te lo dirà. Le scatole sono magiche; sono il tipo di cosa che contengono.

Come ho detto prima, piuttosto che cercare di rendere la boxe più economica, semplicemente non farlo in primo luogo.

+0

Ci sono situazioni in cui la boxe è molto difficile da evitare. Una di queste situazioni è quando si sta costruendo un framework di entità con la registrazione di proprietà statiche e l'accesso al tipo GetValue/SetValue, ad esempio un po 'come gli oggetti/proprietà di dipendenza WPF. Probabilmente finiremo per sferrare un po 'di magia voodoo di "Reflection.Emit" per evitare del tutto la boxe, ma a questo punto è un'impresa enorme ... vedi la risposta qui sotto per la cache della scatola che abbiamo risolto nel frattempo :) –

2

Non esiste una classe come Box<T> per impostazione predefinita. Il tipo è ancora il tipo originale, ma è un riferimento. Poiché Decimal immutable non è possibile modificare il valore dopo la creazione. Quindi non puoi usare il pool con decimali in scatola normali.

È possibile evitare il pugilato per i valori che si verificano ricorrendo all'implementazione di una cache. O è necessario implementare il proprio tipo di box.

il proprio tipo di dialogo non può essere Unboxed da object con un cast di serie però. Quindi dovrai adattare il codice che consuma.


Scrivere un proprio metodo che restituisce un valore boxed:

[ThreadStatic] 
private static Dictionary<T,object> cache; 

public object BoxIt<T>(T value) 
    where T:struct 
{ 
    if(cache==null) 
    cache=new Dictionary<T,object>(); 
    object result; 
    if(!cache.TryGetValue(T,result)) 
    { 
    result=(object)T; 
    cache[value]=result; 
    } 
    return result; 
} 

Un problema con questo semplice implementazione è che la cache non rimuove gli elementi. Cioè è una perdita di memoria.

+0

In che modo questa cache può essere di aiuto? Crea ancora gli stessi oggetti, ma non li rilascia mai. – configurator

+0

Non crea oggetti se si utilizzano spesso gli stessi valori. Ma se la maggior parte dei valori è unica, sarà male. Dato che non so cosa stia facendo l'OP, non so se una cache sarebbe utile. Java usa un modello simile internamente quando intona piccoli numeri interi (da -128 a 127 se ricordo correttamente). – CodesInChaos

0
int i = 5; 
object boxedInt = i; 

Assegnazione di un tipo di valore a un System.Object è più o meno tutto quello che c'è alla boxe per quanto riguarda il codice è interessato (non voglio entrare nel funzionamento boxe dettagli tecnici).

Mantenere i valori decimali in System.Object variabili possono radersi un po 'di tempo dal pugilato, e la creazione di System.Object casi, ma si ha sempre di unboxing. Ciò diventa sempre più difficile se è necessario modificare tali valori frequentemente poiché ogni modifica è un compito, e quindi almeno una boxe.

V'è un esempio di questa pratica anche se - il framework .Net utilizza i valori booleani pre-boxed internamente a un corso simile a questo:

class BoolBox 
{ 
    // boxing here 
    private static object _true = true; 
    // boxing here 
    private static object _false = false; 

    public static object True { get { return _true; } } 
    public static object False { get { return _false; } } 
} 

Il sistema WPF utilizza spesso System.Object variabili per le proprietà di dipendenza, basta per nominare un caso in cui la boxe/unboxing è inevitabile anche in questi "tempi moderni".

4

Il runtime fa più o meno quello che descrivi, tuttavia i generici non sono coinvolti, in quanto i farmaci generici non facevano parte del framework originale.

Non c'è molto che si possa fare a proposito della boxe, se si utilizza del codice che prevede valori in scatola. È possibile creare un oggetto che utilizza la stessa sintassi per restituire un valore sostituendo la conversione implicita, ma sarebbe comunque necessario che fosse un oggetto e in pratica si eseguirà comunque la stessa quantità di lavoro.

Provare a raggruppare valori in scatola molto probabilmente peggiorerà le prestazioni anziché aumentarle. Il garbage collector è creato appositamente per gestire gli oggetti di breve durata in modo efficiente e, se si mettono gli oggetti in un pool, saranno invece oggetti longevi. Quando gli oggetti sopravvivono a una garbage collection, verranno spostati nell'heap successivo, che implica la copia dell'oggetto da una posizione in memoria a un'altra. Quindi, mettendo in comune l'oggetto si può effettivamente causare molto più lavoro per il garbage collector invece di meno.

+0

La tua risposta è giusta, ma la strategia di raggruppamento ha senso in molti casi. Sì, si finisce con oggetti più longevi, che sono costosi perché sopravvivono a molte collezioni. Ma se tutto è raggruppato, allora ci sono pochissime collezioni perché nulla sta facendo pressione sulla raccolta. L'idea di raggruppare non è di diminuire il costo per oggetto della raccolta; piuttosto, l'idea è di diminuire il numero totale di collezioni. –

+0

Sì, il pooling può essere d'aiuto in alcune situazioni, ma il raggruppamento dei valori decimali non dovrebbe produrre molti risultati per valori già esistenti. Se gli stessi valori venissero usati più volte, il risultato della memorizzazione nella cache sarebbe molto più efficiente della memorizzazione nella cache dei parametri di input. – Guffa

+0

Concordo sul fatto che il raggruppamento * per valore * non sia probabilmente una buona idea. Ma potresti mettere insieme solo la scatola; quando la scatola non viene più utilizzata, torna alla piscina e si svuota. Usiamo questa tecnica nel team del compilatore per ridurre la pressione di raccolta.Il lato negativo è che si perdono molti dei vantaggi dello storage raccolto automaticamente; funziona solo se la maggior parte delle volte sei esplicito nel dire quando hai finito con una scatola in modo che possa tornare in piscina. –

0

Purtroppo non è possibile agganciare il processo di boxing, tuttavia è possibile utilizzare le conversioni implicite a proprio vantaggio per renderlo 'simile' al boxing.

Vorrei anche evitare di memorizzare ogni valore in un Dictionary - il tuo problema di memoria peggiorerà. Ecco una struttura di boxe che potrebbe essere utile.

public class Box 
{ 
    internal Box() 
    { } 

    public static Box<T> ItUp<T>(T value) 
     where T : struct 
    { 
     return value; 
    } 

    public static T ItOut<T>(object value) 
     where T : struct 
    { 
     var tbox = value as Box<T>; 
     if (!object.ReferenceEquals(tbox, null)) 
      return tbox.Value; 
     else 
      return (T)value; 
    } 
} 

public sealed class Box<T> : Box 
    where T : struct 
{ 
    public static IEqualityComparer<T> EqualityComparer { get; set; } 
    private static readonly ConcurrentStack<Box<T>> _cache = new ConcurrentStack<Box<T>>(); 
    public T Value 
    { 
     get; 
     private set; 
    } 

    static Box() 
    { 
     EqualityComparer = EqualityComparer<T>.Default; 
    } 

    private Box() 
    { 

    } 

    ~Box() 
    { 
     if (_cache.Count < 4096) // Note this will be approximate. 
     { 
      GC.ReRegisterForFinalize(this); 
      _cache.Push(this); 
     } 
    } 

    public static implicit operator Box<T>(T value) 
    { 
     Box<T> box; 
     if (!_cache.TryPop(out box)) 
      box = new Box<T>(); 
     box.Value = value; 
     return box; 
    } 

    public static implicit operator T(Box<T> value) 
    { 
     return ((Box<T>)value).Value; 
    } 

    public override bool Equals(object obj) 
    { 
     var box = obj as Box<T>; 
     if (!object.ReferenceEquals(box, null)) 
      return EqualityComparer.Equals(box.Value, Value); 
     else if (obj is T) 
      return EqualityComparer.Equals((T)obj, Value); 
     else 
      return false; 
    } 

    public override int GetHashCode() 
    { 
     return Value.GetHashCode(); 
    } 

    public override string ToString() 
    { 
     return Value.ToString(); 
    } 
} 

// Sample usage: 
var boxed = Box.ItUp(100); 
LegacyCode(boxingIsFun); 
void LegacyCode(object boxingIsFun) 
{ 
    var value = Box.ItOut<int>(boxingIsFun); 
} 

In tutta onestà si dovrebbe fare un'altra domanda - e chiedere consigli su come sbarazzarsi di questo problema boxe che avete.

0

Ho appena parlato della nostra implementazione della cache di caselle che utilizziamo nel nostro motore di database degli oggetti. Decimali hanno una vasta gamma di valori che scatole di memorizzazione nella cache non sarebbe molto efficace tale, ma se vi trovate necessariamente utilizzando campi oggetto o un oggetto [] array per memorizzare un sacco di valori comuni, questo potrebbe aiutare:

http://www.singulink.com/CodeIndex/post/value-type-box-cache

L'utilizzo della memoria è caduto come un matto dopo averlo usato. Essenzialmente, tutto nel database è archiviato in matrici object [] e può essere di molti GB di dati, quindi è stato estremamente vantaggioso.

Ci sono tre metodi principali che si vuole utilizzare:

  • object BoxCache<T>.GetBox(T value) - Ottiene una scatola per il valore, se si è in cache altrimenti scatole it IT per voi.
  • object BoxCache<T>.GetOrAddBox(T value) - Ottiene una casella per il valore se uno è memorizzato nella cache altrimenti ne aggiunge uno nella cache e lo restituisce.
  • void AddValues<T>(IEnumerable<T> values) - Aggiunge caselle per i specificati valori nella cache.
Problemi correlati