2012-03-28 14 views
34

Per il codice come questo:Perché vengono generate istruzioni AND?

int res = 0; 
for (int i = 0; i < 32; i++) 
{ 
    res += 1 << i; 
} 

Questo codice viene generato (modalità di rilascio, senza debugger, 64bit):

xor edx,edx 
mov r8d,1 
_loop: 
lea ecx,[r8-1] 
and ecx,1Fh  ; why? 
mov eax,1 
shl eax,cl 
add edx,eax 
mov ecx,r8d 
and ecx,1Fh  ; why? 
mov eax,1 
shl eax,cl 
add edx,eax 
lea ecx,[r8+1] 
and ecx,1Fh  ; why? 
mov eax,1 
shl eax,cl 
add edx,eax 
lea ecx,[r8+2] 
and ecx,1Fh  ; why? 
mov eax,1 
shl eax,cl 
add edx,eax 
add r8d,4 
cmp r8d,21h 
jl _loop 

ora posso vedere il punto di maggior parte delle istruzioni lì, ma ciò che è con le istruzioni AND? ecx sarà mai essere più di 0x1F in questo codice comunque, ma lo scuso per non averlo notato (e anche per non notare che il risultato è una costante), non è un compilatore in anticipo che può permettersi di spendere molto tempo dopo l'analisi. Ma ancora più importante, SHL con un operando a 32 bit maschera già per 0x1F. Quindi mi sembra che questi AND siano completamente inutili. Perché vengono generati? Hanno uno scopo che mi manca?

+2

Per motivi di interesse, hai già un collegamento su SHL che maschera? Ho cercato in un paio di posti ma non sono riuscito a trovare alcuna dichiarazione in tal senso. (Colpiscilo - trovalo.) –

+0

Non un esperto - quindi scusa se domanda stupida, ma come si popola il registro 'cl'. L'operazione 'e' è il modo più veloce per farlo, forse? – weston

+0

Questo intero codice generato mi sembra giberiano. perché rompere un semplice batch di istruzioni in così tanti sottogruppi? Non ho studio visivo disponibile in questo momento, ma ho davvero difficoltà a credere che il compilatore JIT produca questo tipo di assemblaggio ..... – Polity

risposta

27

Il and è già presente nel codice CIL emessa dal compilatore C#:

IL_0009: ldc.i4.s 31 
    IL_000b: and 
    IL_000c: shl 

Le specifiche per l'istruzione CIL shl dice:

Il valore di ritorno non è specificato se shiftAmount è maggiore o uguale alla dimensione del valore .

Il C# spec, tuttavia, definisce il passaggio 32 bit per prendere il passaggio contare mod 32:

Quando il tipo di x è int o uint, il valore di scorrimento è dato dal cinque bit di conto basso. In altre parole, il numero di turni è calcolato da count & 0x1F.

In questa situazione, il compilatore C# non può davvero fare molto meglio di emettere un'esplicita operazione and. La cosa migliore che puoi sperare è che JITter noterà questo e ottimizzerà il ridondante and, ma ciò richiede tempo e la velocità di JIT è piuttosto importante. Quindi considera questo il prezzo pagato per un sistema basato su JIT.

La vera domanda, immagino, è il motivo per cui CIL specifica l'istruzione shl in questo modo, quando C# e x86 specificano entrambi il comportamento di troncamento. Che io non lo so, ma suppongo che sia importante per le specifiche CIL evitare di specificare un comportamento che può JIT a qualcosa di costoso su alcuni set di istruzioni. Allo stesso tempo, è importante che C# abbia il minor numero possibile di comportamenti non definiti, perché la gente finisce invariabilmente per usare comportamenti così indefiniti fino alla prossima versione del compilatore/framework/OS/qualunque cosa li cambi, infrangendo il codice.

+0

Su x64, shl tronca il valore di shift a 64 bit che può portare a brutte sorprese quando si spostano valori a 64 bit e si passa da x86 a x64 . –

+1

Se il 'shl' CIL era definito come troncato, sicuramente la cosa più costosa che doveva essere fatta era la stessa E non è stato comunque evitato ora? – harold

+0

@IgorSkochinsky L'ho preso in considerazione, ma non ho una specifica x64 a portata di mano, e l'unico riferimento che potrei trovare _seemed_ per suggerire che un 'shl eax' troncherà a 32, proprio come farebbe su x86. Non è così? –

5

Il compilatore C# deve inserire queste istruzioni AND durante la generazione del codice intermedio (indipendente dalla macchina), poiché l'operatore di spostamento a sinistra C# è necessario per utilizzare solo 5 bit meno significativi.

Durante la generazione del codice x86, l'ottimizzazione del compilatore può eliminare queste istruzioni non necessarie. Ma, apparentemente, salta questa ottimizzazione (probabilmente, perché non può permettersi di dedicare molto tempo all'analisi).

10

core x64 applicano già la maschera a 5 bit alla quantità di spostamento.Dal manuale del processore Intel, volume 2B pagina 4-362:

L'operando di destinazione può essere un registro o una posizione di memoria. L'operando di conteggio può essere un valore immediato o il registro CL. Il conteggio è mascherato a 5 bit (o 6 bit se in modalità 64 bit e viene utilizzato REG.W). Una codifica opcode speciale viene fornita per un conteggio di 1.

Quindi questo è il codice della macchina che non è necessario. Sfortunatamente, il compilatore C# non può formulare alcuna ipotesi sul comportamento del processore e deve applicare le regole del linguaggio C#. E generare IL il cui comportamento è specificato nelle specifiche CLI. Ecma-335, Partion III, capitolo 3.58 dice questo circa il codice operativo SHL:

L'istruzione SHL sposta il valore (Int32, Int64 o native int) ha lasciato per il numero di bit specificato da shiftAmount. shiftAmount è di tipo int32 o nativo int. Il valore restituito non è specificato se shiftAmount è maggiore o uguale alla larghezza del valore.

Non specificato è il punto qui. Il bullonaggio del comportamento specificato in cima a dettagli di implementazione non specificati produce il codice non necessario. Tecnicamente il jitter potrebbe ottimizzare il codice operativo. Anche se è difficile, non conosce la regola della lingua. Qualsiasi linguaggio che specifica il mascheramento avrà difficoltà a generare IL corretto. È possibile pubblicare su connect.microsoft.com per ottenere la vista della squadra di jitter in merito.

Problemi correlati