Para ampliar en Eric Petroelje's answer.
Si reescribimos el programa de la siguiente manera (el comportamiento es idéntico, pero evitar la función lambda hace que sea más fácil leer el desmontaje), podemos separarlo y ver lo que realmente significa "almacenar en caché el valor de un campo 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();
}
}
obtenemos el siguiente comportamiento:
Foo.Complete is a Field | Foo.Complete is a Property
x86-RELEASE | loops forever | completes
x64-RELEASE | completes | completes
en x86 de liberación, el JIT CLR compila el tiempo (foo.Complete!) en este código:
Completo es 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
Las 2 últimas líneas son el problema. Si eax es cero, entonces se quedará allí en un ciclo infinito que dice "¿EAX es cero?", sin ningún código que cambie el valor de eax.
completa es una propiedad:
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
En realidad, esto se parece más bonito de código. Mientras el JIT ha introducido el getter de propiedad (de lo contrario, vería algunas instrucciones call
yendo a otras funciones) en un código simple para leer el campo Complete
directamente, porque no está permitido almacenar en caché la variable, cuando genera el bucle, lee repetidamente la memoria una y otra vez, en lugar de sólo lectura inútilmente el registro
en x64 de liberación, el JIT CLR de 64 bits compila el tiempo (! foo.Complete) en el código
completa es 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 es una propiedad
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
El JIT de 64 bits es hacer la misma cosa para ambas propiedades y campos, excepto cuando se trata de un campo que está "desenrollada" la primera iteración del bucle - esto básicamente pone un if(foo.Complete) { jump past the loop code }
delante de él por alguna razón.
En ambos casos, es hacer una cosa similar a la JIT x 86 cuando se trata de una propiedad:
- Se inlines el método a una memoria de lectura directa - No almacenar en caché, y vuelve a leer el valor cada vez
No estoy seguro de si el CLR de 64 bits no puede almacenar el valor del campo en el registro como el de 32 bits, pero si lo es, no se molesta en hacerlo. Tal vez lo hará en el futuro?
En cualquier caso, esto ilustra cómo el comportamiento depende de la plataforma y está sujeto a cambios. Espero que ayude :-)
El título de su pregunta es más amplio de lo que necesita ser para cubrir el material en cuestión. No todos los códigos son tan simples como esto. –
¿Has comparado el IL de ambos programas? – Oded
sí comparé el IL, pero realmente no vi nada que pudiera darme una pista – dmg