2015-06-18 24 views
5

consideri un metodo che restituisce una struttura, come questo:In che modo .NET ottimizzano i metodi che restituiscono un valore di Struttura?

Public Function DoWork() As MyStructure 

    Return New MyStructure(1.5, 1.7, 1.1, 55.9) 

End Function 

In questo caso, non .NET creare e inizializzare un valore MyStructurevolta, o due volte ?

MODIFICA: La mia impressione è che una chiamata a DoWork deve coinvolgere .NET che spinge il valore di ritorno in pila fin dall'inizio. Altrimenti, in che modo Return restituisce qualcosa al codice chiamante? Quindi questa è la prima inizializzazione di cui sto parlando.

La seconda inizializzazione sarebbe al Return dichiarazione, in cui gli argomenti 1.5, 1.7, 1.1, 55.9 inizializzare un nuovo valore MyStructure. Su Return, .NET quindi sovrascrivere il valore esistente nello Stack con il nuovo valore restituito.

Il fatto è che so ben poco di come .NET funzioni sotto il cofano. La mia idea di come funziona Stacks si basa su vaghi ricordi del tentativo di codificare in Pascal, sotto DOS, nei primi anni '90. Non ho idea di come .NET/Windows funzioni in questi giorni!

+0

Ho testato di recente entrambe le JIT per evitare le copie della struttura. Non ho verificato i ritorni di funzione, li ho dimenticati. Ma all'interno di un metodo quasi tutto ciò che riguarda le strutture viene tradotto letteralmente mentre lo scrivi. Evitamento della copia molto piccolo anche in casi ovvi. Quindi non ti illudere, non molte ottimizzazioni nei JIT .NET. – usr

+0

Se fosse due volte, quale sarebbe la seconda volta (dopo il 'New')? –

+0

@JohnSaunders forse il risultato del nuovo viene prima scritto in una nuova posizione dello stack e solo allora il ritorno verrebbe eseguito copiando il valore temporaneo nel percorso del valore di ritorno. Penso che sarebbe quello che farebbe un JIT non ottimizzante naturalmente. – usr

risposta

5

Basta dare un'occhiata al codice macchina generato per vedere cosa succede. Per prima cosa è necessario modificare un'opzione per garantire che l'ottimizzatore sia abilitato, Strumenti> Opzioni> Debug> Generale> deseleziona la casella di controllo "Elimina ottimizzazione JIT". Passa alla build Release. Impostare un punto di interruzione sulla chiamata DoWork e quando viene colpito usa Debug> Windows> Disassembly per vedere il codice macchina generato.

Farò un po 'di noodle su quello che vedi. Il tuo istinto è corretto, solo restituire semplici valori scalari è efficiente, si inseriscono in un registro della CPU. Non questa struttura, il metodo del chiamante deve riservare spazio sul suo stack frame in modo che il callee possa memorizzare la struttura lì. Passa un puntatore a quello spazio riservato. In altre parole, non c'è alcuna differenza se hai dichiarato questo metodo come Public Sub DoWork(ByRef retval As MyStructure).

Solo l'altro modo in cui è possibile ottimizzare questo codice consiste nell'integrare la funzione. Il jitter x86 non lo fa, è schizzinoso sui metodi che restituiscono le strutture. Il jitter x64 in effetti integra il metodo, ma poi fa un lavoro assolutamente orribile. Si riserva ancora lo spazio per il valore di ritorno e quindi genera un sacco e un sacco di inizializzazione inutile e il valore del codice in movimento. Abbastanza atroce Questo jitter è stato riscritto per VS2015 (nome progetto RyuJIT), non ho ancora installato per vedere se fa un lavoro migliore.

Conclusione di base per disegnare: questo codice non è ottimizzato.Le strutture funzionano bene quando le passate ByVal. Non lasciare che questo crampi il tuo stile, stiamo parlando di nanosecondi qui.

+0

Non hai appena descritto che * è * in effetti ottimizzato? Sembra che la struttura non venga affatto copiata. – Luaan

+0

Erm, non sono sicuro di dove lo hai preso. Non ti aspetteresti alcuna copia e il comportamento ByRef, il jitter x64 armeggia. –

+0

Bene, hai detto: 'Non questa struttura, il metodo del chiamante deve riservare spazio sul suo stack frame in modo che il destinatario possa memorizzare la struttura lì. Passa un puntatore a quello spazio riservato. In altre parole, non c'è alcuna differenza se si dichiarava questo metodo come Public Sub DoWork (ByRef retval As MyStructure). "Capisco che nel senso che" sia passato direttamente allo spazio stack assegnato dal chiamante ". Forse intendevi "sia il chiamante che il destinatario devono allocare lo stack space e quindi le copie del callee nello spazio stack del chiamante"? – Luaan

0

Osservando ciò che viene fatto in IL per struct di un tipo BCL, sembra essere creato una sola volta.
Esempio con Size.Truncate

public static Size Truncate(SizeF value) 
{ 
    return new Size((int)value.Width, (int)value.Height); 
} 
.method public hidebysig static 
valuetype System.Drawing.Size Truncate (
    valuetype System.Drawing.SizeF 'value' 
) cil managed 
{ 
    // Method begins at RVA 0x169d6 
    // Code size 22 (0x16) 
    .maxstack 8 

    IL_0000: ldarga.s 'value' 
    IL_0002: call instance float32 System.Drawing.SizeF::get_Width() 
    IL_0007: conv.i4 
    IL_0008: ldarga.s 'value' 
    IL_000a: call instance float32 System.Drawing.SizeF::get_Height() 
    IL_000f: conv.i4 
    IL_0010: newobj instance void System.Drawing.Size::.ctor(int32, int32) 
    IL_0015: ret 
} // end of method Size::Truncate 

si può vedere a IL_0010 la newobj codice operativo ed è documentation stati:

Crea un nuovo oggetto o un nuova istanza di un tipo di valore, spingendo un riferimento oggetto (tipo O) sullo stack di valutazione.

+1

Vero, ma questo descrive solo la "macchina virtuale" - il punto chiave è come l'IL viene tradotto in codice macchina. Se passassi da solo IL, penseresti che tutto sia fatto in pila in .NET, che semplicemente non è vero. – Luaan

Problemi correlati