2015-01-21 13 views
15

Sto scrivendo un programma intensivo di calcolo con VB.NET 2010 e desidero ottimizzare la velocità. Trovo che gli operatori AndAlso e OrElse siano anomali lenti se il risultato dell'operazione è assegnato a una variabile di livello di classe. Ad esempio, mentre le dichiarazioniAndAlso OrElse può essere anomalo lentamente

a = _b AndAlso _c 
_a = a 

occorrono circa 6 cicli macchina tra loro nel exe compilato, la singola istruzione

_a = _b AndAlso _c 

dura circa 80 cicli macchina. Qui _a, _b e _c sono variabili booleane private di Form1 e le istruzioni in questione sono in una procedura di istanza di Form1, di cui a è una variabile booleana locale.

Non riesco a trovare il motivo per cui la singola istruzione impiega così tanto tempo. Ho esplorato usando NetReflector fino al livello del codice CIL, che sembra buono:

Instruction    Explanation        Stack 
00: ldarg.0    Push Me (ref to current inst of Form1) Me 
01: ldarg.0    Push Me         Me, Me 
02: ldfld bool Form1::_b Pop Me, read _b and push it    _b, Me 
07: brfalse.s 11   Pop _b; if false, branch to 11   Me 
09: ldarg.0    (_b true) Push Me      Me, Me 
0a: ldfld bool Form1::_c (_b true) Pop Me, read _c and push it _c, Me 
0f: brtrue.s 14   (_b true) Pop _c; if true, branch to 14 Me 
11: ldc.i4.0    (_b, _c not both true) Push result 0  result, Me 
12: br.s 15    Jump unconditionally to 15    result, Me 
----- 
14: ldc.i4.1    (_b, _c both true) Push result 1   result, Me 
15: stfld bool Form1::_a Pop result and Me; write result to _a (empty) 
1a: 

Qualcuno può far luce sul motivo per cui la dichiarazione _a = _b AndAlso _c prende 80 cicli macchina al posto del previsto 5 o giù di lì?

Sto usando Windows XP con .NET 4.0 e Visual Studio Express 2010. Ho misurato i tempi con uno snippet francamente sporco che utilizza fondamentalmente un oggetto Stopwatch per temporizzare un ciclo For-Next con 1000 iterazioni contenenti il codice in questione e confrontarlo con un ciclo For-Next vuoto; include un'istruzione inutile in entrambi i cicli per sprecare alcuni cicli e impedire lo stallo del processore. Grezzo ma abbastanza buono per i miei scopi

+0

Scusate se questo commento sembra a qualcuno, ma se stavo cercando velocità di calcolo e tempo di misurazione in cicli, probabilmente non userei VB.NET o .NET in generale. – TyCobb

+0

forse se pubblichi più del codice, possiamo offrire altri suggerimenti per migliorare l'efficienza. – Jeremy

+0

Hai provato a utilizzare solo AND? Soprattutto perché stai lavorando con la variabile Booleans. –

risposta

12

Ci sono due fattori in gioco che rendono questo codice lento. Non puoi vederlo dall'IL, solo il codice macchina può darti un'idea.


Il primo è quello generale associato all'operatore AndAlso. È un operatore di cortocircuito, l'operando di destra non viene valutato se l'operando di sinistra vale False. Ciò richiede un ramo nel codice macchina. La ramificazione è una delle cose più lente che un processore possa fare, deve indovinare nel ramo in avanti per evitare il rischio di dover scaricare la tubazione. Se si sbaglia, allora ci vorrà un grande successo. Molto ben coperto in this post. La tipica perdita di perf se la variabile a è altamente casuale e il ramo quindi scarsamente previsto, è intorno al 500%.

Si evita questo rischio utilizzando l'operatore And, invece, non richiede un ramo nel codice macchina. È solo una singola istruzione, E è implementata dal processore. Non ha senso favorire AndAnso in un'espressione del genere, niente va storto se viene valutato l'operando di destra. Non applicabile qui, ma anche se l'IL mostra un ramo, il jitter potrebbe comunque rendere il codice macchina senza branch con un'istruzione CMOV (spostamento condizionato).


Ma la cosa più significativa nel tuo caso è che la classe Form eredita dalla classe MarshalByRefObject. La catena di ereditarietà è MarshalByRefObject> Component> Control> ScrollableControl> ContainerControl> Form.

MBRO viene elaborato appositamente dal compilatore Just-in-Time, il codice potrebbe funzionare con un proxy per l'oggetto classe con l'oggetto reale che vive in un altro AppDomain o in un'altra macchina.Un proxy è trasparente al jitter per quasi tutti i tipi di membri della classe, sono implementati come semplici chiamate di metodo. Tranne i campi, non possono essere inoltrati perché l'accesso ad un campo è fatto con una memoria di lettura/scrittura, non con una chiamata di metodo. Se il jitter non è in grado di dimostrare che l'oggetto è locale, è costretto a chiamare nel CLR, utilizzando i metodi helper denominati JIT_GetFieldXxx() e JIT_SetFieldXxx(). Il CLR sa se il riferimento all'oggetto è un proxy o il vero affare e tratta la differenza. Il sovraccarico è abbastanza consistente, 80 cicli suona bene.

Non c'è molto che si possa fare a tale proposito fintanto che le variabili sono membri della classe Form. Spostarli in una classe di supporto è la soluzione.

+0

Bello! Grazie per la tua eleganza :) – Jeremy

+3

Meraviglioso! Grazie mille. Quando sposto le variabili in una classe helper, l'istruzione richiede in media 3 cicli e mezzo anziché 80. –

+2

Risultato piacevole. Q + A non può essere migliore di questo. Aggiorna la tua domanda e spendi alcune parole su come hai misurato, non abbastanza programmatori. –

Problemi correlati