Per espandere su Eric Petroelje's answer.
Se riscriviamo il programma come segue (il comportamento è identico, ma evitando la funzione lambda rende più semplice la lettura del disassemblaggio), possiamo dissasigliarlo e vedere cosa significa in realtà "memorizzare il valore di un campo in un registro"
class Foo
{
public bool Complete; // { get; set; }
}
class Program
{
static Foo foo = new Foo();
static void ThreadProc()
{
bool toggle = false;
while (!foo.Complete) toggle = !toggle;
Console.WriteLine("Thread done");
}
static void Main()
{
var t = new Thread(ThreadProc);
t.Start();
Thread.Sleep(1000);
foo.Complete = true;
t.Join();
}
}
otteniamo il seguente comportamento:
Foo.Complete is a Field | Foo.Complete is a Property
x86-RELEASE | loops forever | completes
x64-RELEASE | completes | completes
in x86-release, il JIT CLR compila il tempo (foo.Complete!) in questo codice:
Completo è un campo:
004f0153 a1f01f2f03 mov eax,dword ptr ds:[032F1FF0h] # Put a pointer to the Foo object in EAX
004f0158 0fb64004 movzx eax,byte ptr [eax+4] # Put the value pointed to by [EAX+4] into EAX (this basically puts the value of .Complete into EAX)
004f015c 85c0 test eax,eax # Is EAX zero? (is .Complete false?)
004f015e 7504 jne 004f0164 # If it is not, exit the loop
# start of loop
004f0160 85c0 test eax,eax # Is EAX zero? (is .Complete false?)
004f0162 74fc je 004f0160 # If it is, goto start of loop
Le ultime 2 righe rappresentano il problema. Se eax è pari a zero, allora si limiterà a sedersi in un ciclo infinito che dice "è EAX zero?", senza che alcun codice modifichi mai il valore di eax!
Complete è una proprietà:
00220155 a1f01f3a03 mov eax,dword ptr ds:[033A1FF0h] # Put a pointer to the Foo object in EAX
0022015a 80780400 cmp byte ptr [eax+4],0 # Compare the value at [EAX+4] with zero (is .Complete false?)
0022015e 74f5 je 00220155 # If it is, goto 2 lines up
Questo appare in realtà come il codice più bello. Mentre il JIT ha delineato il getter della proprietà (altrimenti vedresti alcune istruzioni call
che si spostano su altre funzioni) in un semplice codice per leggere direttamente il campo Complete
, perché non è consentito memorizzare nella cache la variabile, quando genera il ciclo, più volte si legge la memoria più e più volte, piuttosto che solo inutilmente la lettura del registro
in x64-release, il CLR JIT 64 bit compila il tempo (! foo.Complete) in questo codice
Complete è un campo :
00140245 48b8d82f961200000000 mov rax,12962FD8h # put 12E12FD8h into RAX. 12E12FD8h is a pointer-to-a-pointer in some .NET static object table
0014024f 488b00 mov rax,qword ptr [rax] # Follow the above pointer; puts a pointer to the Foo object in RAX
00140252 0fb64808 movzx ecx,byte ptr [rax+8] # Add 8 to the pointer to Foo object (it now points to the .Complete field) and put that value in ECX
00140256 85c9 test ecx,ecx # Is ECX zero ? (is the .Complete field false?)
00140258 751b jne 00140275 # If nonzero/true, exit the loop
0014025a 660f1f440000 nop word ptr [rax+rax] # Do nothing!
# start of loop
00140260 48b8d82f961200000000 mov rax,12962FD8h # put 12E12FD8h into RAX. 12E12FD8h is a pointer-to-a-pointer in some .NET static object table
0014026a 488b00 mov rax,qword ptr [rax] # Follow the above pointer; puts a pointer to the Foo object in RAX
0014026d 0fb64808 movzx ecx,byte ptr [rax+8] # Add 8 to the pointer to Foo object (it now points to the .Complete field) and put that value in ECX
00140271 85c9 test ecx,ecx # Is ECX Zero ? (is the .Complete field true?)
00140273 74eb je 00140260 # If zero/false, go to start of loop
Complete è una proprietà
00140250 48b8d82fe11200000000 mov rax,12E12FD8h # put 12E12FD8h into RAX. 12E12FD8h is a pointer-to-a-pointer in some .NET static object table
0014025a 488b00 mov rax,qword ptr [rax] # Follow the above pointer; puts a pointer to the Foo object in RAX
0014025d 0fb64008 movzx eax,byte ptr [rax+8] # Add 8 to the pointer to Foo object (it now points to the .Complete field) and put that value in EAX
00140261 85c0 test eax,eax # Is EAX 0 ? (is the .Complete field false?)
00140263 74eb je 00140250 # If zero/false, go to the start
Il JIT 64-bit sta facendo la stessa cosa per entrambe le proprietà ei campi, tranne quando si tratta di un campo che è "srotolato" la prima iterazione del ciclo - questo mette in pratica un if(foo.Complete) { jump past the loop code }
di fronte ad essa per un po ' ragionare.
In entrambi i casi, si tratta di fare una cosa simile al JIT x86 quando si tratta di una proprietà:
- E 'inline il metodo a una memoria diretta lettura - E non memorizza nella cache, e rilegge il valore ogni volta
Non sono sicuro che il CLR a 64 bit non sia autorizzato a memorizzare nella cache il valore del campo nel registro come fa il 32 bit, ma se lo è, non si preoccupa di farlo. Forse lo farà in futuro?
In ogni caso, questo illustra come il comportamento dipende dalla piattaforma e soggetto a modifiche. Spero che aiuti :-)
Il titolo della tua domanda è più ampio di quello che deve essere per coprire il materiale in questione. Non tutto il codice è così semplice come questo. –
Hai confrontato l'IL di entrambi i programmi? – Oded
ho confrontato l'IL ma non ho visto nulla che potesse indurmi a una spiegazione – dmg