Domanda

Sto realizzando un progetto hobby con raytracer e originariamente utilizzavo strutture per i miei oggetti Vector e Ray e ho pensato che un raytracer fosse la situazione perfetta per usarli:ne crei milioni, non vivono più di un singolo metodo, sono leggeri.Tuttavia, cambiando semplicemente "struct" in "class" su Vector e Ray, ho ottenuto un miglioramento delle prestazioni molto significativo.

Cosa dà?Sono entrambi piccoli (3 float per Vector, 2 Vector per un Ray), non vengono copiati eccessivamente.Ovviamente li passo ai metodi quando necessario, ma è inevitabile.Quindi quali sono le insidie ​​​​comuni che interrompono le prestazioni quando si utilizzano le strutture?ho letto Questo Articolo MSDN che dice quanto segue:

Quando esegui questo esempio, vedrai che il ciclo struct è più veloce di diversi ordini di grandezza.Tuttavia, è importante fare attenzione a non utilizzare ValueType quando li tratti come oggetti.Ciò aggiunge ulteriore carico di boxing e unboxing al tuo programma e può finire per costarti più di quanto ti costerebbe se fossi rimasto bloccato con gli oggetti!Per vederlo in azione, modifica il codice sopra per utilizzare una serie di foos e barre.Scoprirai che le prestazioni sono più o meno uguali.

È comunque piuttosto vecchio (2001) e l'intero "metterli in un array provoca boxing/unboxing" mi è sembrato strano.È vero?Tuttavia, ho precalcolato i raggi primari e li ho inseriti in un array, quindi ho ripreso questo articolo e ho calcolato il raggio primario quando ne avevo bisogno e non li ho mai aggiunti a un array, ma non ha cambiato nulla:con le lezioni era ancora 1,5 volte più veloce.

Sto eseguendo .NET 3.5 SP1 che credo abbia risolto un problema per cui i metodi struct non erano mai in linea, quindi non può essere neanche questo.

Quindi in poche parole:qualche consiglio, cose da considerare e cosa evitare?

MODIFICARE:Come suggerito in alcune risposte, ho creato un progetto di prova in cui ho provato a passare le strutture come rif.I metodi per aggiungere due vettori:

public static VectorStruct Add(VectorStruct v1, VectorStruct v2)
{
  return new VectorStruct(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
}

public static VectorStruct Add(ref VectorStruct v1, ref VectorStruct v2)
{
  return new VectorStruct(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
}

public static void Add(ref VectorStruct v1, ref VectorStruct v2, out VectorStruct v3)
{
  v3 = new VectorStruct(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
}

Per ognuno ho ottenuto una variazione del seguente metodo di benchmark:

VectorStruct StructTest()
{
  Stopwatch sw = new Stopwatch();
  sw.Start();
  var v2 = new VectorStruct(0, 0, 0);
  for (int i = 0; i < 100000000; i++)
  {
    var v0 = new VectorStruct(i, i, i);
    var v1 = new VectorStruct(i, i, i);
    v2 = VectorStruct.Add(ref v0, ref v1);
  }
  sw.Stop();
  Console.WriteLine(sw.Elapsed.ToString());
  return v2; // To make sure v2 doesn't get optimized away because it's unused. 
}

Sembra che tutti funzionino praticamente identici.È possibile che vengano ottimizzati dal JIT su qualunque sia il modo ottimale per superare questa struttura?

EDIT2:Devo notare, tra l'altro, che utilizzo delle strutture nel mio progetto di test È circa il 50% più veloce rispetto all'utilizzo di una classe.Perché questo è diverso per il mio Raytracer non lo so.

È stato utile?

Soluzione

In sostanza, non farli troppo grande, e passarle in giro da ref quando si può. Ho scoperto questo l'esatto stesso modo ... Cambiando le mie classi Vector e Ray per le strutture.

Con più memoria di essere passati in giro, è destinato a causare la cache botte.

Altri suggerimenti

Un array di struct sarebbe un'unica struttura contiguo in memoria, mentre articoli in un array di oggetti (istanze di tipi di riferimento) devono essere affrontati individualmente da un puntatore (cioè un riferimento a un oggetto sul mucchio garbage collection ). Pertanto, se si utilizzano grandi collezioni di oggetti in una volta, le strutture vi darà un guadagno di prestazioni in quanto hanno bisogno meno indirections. Inoltre, le strutture non possono essere ereditati, che potrebbero permettere al compilatore di effettuare ulteriori ottimizzazioni (ma che è solo una possibilità e dipende dal compilatore).

Tuttavia, le strutture sono piuttosto diverse semantiche di assegnazione e, inoltre, non possono essere ereditate. Perciò Io di solito evitare le strutture, tranne per il dato motivi di prestazioni quando è necessario.


struct

Un array di valori V codificato da una struttura (tipo di valore) aspetto nella memoria:

vvvv

class

Un array di valori V codificato da una classe (tipo di riferimento) aspetto:

pppp

.. v..v ... v.v ..

dove p sono i puntatori questo, o riferimenti, che indicano i valori effettivi V sul mucchio. I puntini indicano altri oggetti che possono essere alternano sul mucchio. Nel caso di tipi di riferimento si deve fare riferimento v tramite il corrispondente p, nel caso di tipi di valore è possibile ottenere il valore direttamente tramite il suo offset nella matrice.

Nelle raccomandazioni per quando utilizzare una struttura che dice che non dovrebbe essere maggiore di 16 byte. Il vostro vettore è di 12 byte, che è vicino al limite. Ray ha due vettori, mettendolo a 24 byte, che è chiaramente il limite consigliato.

Quando una struct diventa più grande di 16 byte non può più essere copiato in modo efficiente con un unico set di istruzioni, invece di un ciclo viene utilizzato. Così, facendo passare questo limite "magia", si sta effettivamente facendo molto più lavoro quando si passa una struttura rispetto a quando si passa un riferimento a un oggetto. Questo è il motivo per cui il codice è più veloce con le classi vedevamo c'è più overhead quando l'allocazione degli oggetti.

Il vettore potrebbe ancora essere una struttura, ma Ray è semplicemente troppo grande per funzionare bene come una struct.

Tutto scritto per quanto riguarda la boxe / unboxing prima di farmaci generici NET può essere assunto con qualcosa di un grano di sale. tipi di raccolta generici hanno rimosso la necessità di boxe e unboxing dei tipi di valore, il che rende l'utilizzo struct in queste situazioni più prezioso.

Per quanto riguarda il tuo rallentamento specifica -. Avremmo probabilmente bisogno di vedere un po 'di codice

Credo che la chiave sta in queste due affermazioni dal tuo post:

  

di creare milioni di loro

e

  

le faccio passare a metodi, se necessario, naturalmente,

Ora meno che la vostra struct è inferiore o uguale a 4 byte di dimensione (o 8 byte se siete su un sistema a 64-bit) si copia molto di più su ogni chiamata di metodo, allora se hai superato semplicemente un riferimento a un oggetto.

La prima cosa che cercherei è assicurarmi di aver implementato esplicitamente Equals e GetHashCode.Non farlo significa che l'implementazione runtime di ciascuno di questi esegue alcune operazioni molto costose per confrontare due istanze di struttura (internamente utilizza la riflessione per determinare ciascuno dei campi privati ​​e quindi ne controlla l'uguaglianza, ciò causa una quantità significativa di allocazione) .

In generale, però, la cosa migliore che puoi fare è eseguire il codice con un profiler e vedere dove sono le parti lente.Può essere un'esperienza che apre gli occhi.

Avete profilata l'applicazione? Profiling è l'unico modo sicuro fuoco per vedere dove il vero problema di prestazioni è. Ci sono operazioni che sono generalmente meglio / peggio le strutture ma a meno che il profilo si era appena essere indovinare da quale sia il problema.

Mentre la funzionalità è simile, le strutture sono di solito più efficienti di classi. È necessario definire una struttura, piuttosto che una classe, se il tipo avrà un rendimento migliore come un tipo di valore di un tipo di riferimento.

In particolare, i tipi di strutture devono soddisfare tutti questi criteri:

  • rappresenta logicamente un singolo valore
  • ha una dimensione esempio meno di 16 byte
  • non sarà cambiato dopo la creazione
  • non sarà gettato per un tipo di riferimento

Io uso le strutture fondamentalmente per gli oggetti dei parametri, tornando più parti di informazioni da una funzione, e ... nient'altro. Non so se sia "giusto" o "sbagliato", ma questo è quello che faccio.

La mia ray tracer inoltre usa i vettori struct (anche se non Raggi) e cambiando vettore di classe non sembra avere alcun impatto sulle prestazioni. Attualmente sto usando tre doppie per il vettore quindi potrebbe essere più grande di quello che dovrebbe essere. Una cosa da notare, però, e questo potrebbe essere ovvio, ma non era per me, ed è quello di eseguire il programma al di fuori di Visual Studio. Anche se si imposta a build di rilascio ottimizzato è possibile ottenere un aumento di velocità enorme se si avvia l'exe al di fuori di VS. Qualsiasi analisi comparativa che fate dovrebbe tenerne conto.

Se le struct sono piccole, e non troppi esistere in una volta, va mettendoli in pila (fintanto che la sua una variabile locale e non un membro di una classe) e non sul mucchio, questo significa che il GC non ha bisogno di essere invocato e allocazione di memoria / deallocazione dovrebbe essere quasi istantanea.

Quando si passa una struttura come parametro di funzionare, la struct viene copiato, il che significa non solo più allocazioni / deallocazioni (dalla pila, che è quasi istantanea, ma ha ancora dall'alto), ma l'overhead di semplice trasferimento di dati tra le 2 copie. Se si passa attraverso di riferimento, questo è un non problema, come si sta solo dicendo che dove leggere i dati da, piuttosto che copiarlo.

Non sono sicuro al 100% su questo, ma ho il sospetto che il ritorno array tramite un parametro di 'fuori' può anche dare un aumento di velocità, come memoria sullo stack è riservato per essa e non ha bisogno di essere copiati come la pila è "srotolata" alla fine di chiamate di funzione.

È inoltre possibile effettuare le strutture in oggetti Nullable. classi personalizzate non saranno in grado di creato

come

Nullable<MyCustomClass> xxx = new Nullable<MyCustomClass>

dove con una struct è annullabile

Nullable<MyCustomStruct> xxx = new Nullable<MyCustomStruct>

Ma sarà (ovviamente) di perdere tutte le vostre caratteristiche di ereditarietà

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