要擴大Eric Petroelje's answer。
如果我們按照以下方式重寫程序(行爲是相同的,但是避免使用lambda函數可以更容易地讀取反彙編),我們可以將其解析並查看它實際上意味着什麼「緩存字段的值一登記」
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();
}
}
我們得到以下行爲:
Foo.Complete is a Field | Foo.Complete is a Property
x86-RELEASE | loops forever | completes
x64-RELEASE | completes | completes
在x86的版本
,CLR的JIT編譯時(foo.Complete!)這個代碼:
完全是場:
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
的最後兩行都是問題。如果eax爲零,那麼它只會坐在一個無限循環中說「EAX零?」,沒有任何代碼可以改變eax的值!
完全是一個屬性:
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
這實際上看起來更好的代碼。雖然JIT內聯了屬性的getter(否則你會看到一些call
說明會切換到其他功能)插入一些簡單的代碼來直接讀取Complete
領域,因爲它不允許緩存變量,當它產生一個循環中,重複一遍又一遍讀取內存,而不僅僅是毫無意義的讀取寄存器
在x64的版本
,64位CLR JIT編譯,而(!foo.Complete)這個代碼
完全是場:
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
完全是一個屬性
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
64位JIT是做同樣的事,這兩個屬性和字段,除非它是一個場它的「展開」循環的第一次迭代 - 這基本上把一個if(foo.Complete) { jump past the loop code }
在它前面的一些原因。
在這兩種情況下,它與物業打交道時做類似的事情在x86 JIT:
- 它內聯的方法,直接存儲器讀取 - 它不緩存它,並重新讀取值每次
我不知道,如果64位CLR是不允許像32位人做的寄存器緩存字段值,但如果是,它沒有打擾這樣做。也許將來呢?
無論如何,這說明行爲是如何依賴於平臺,並隨時可能更改。我希望這有助於:-)
你的問題的標題是更廣泛的比它需要的是覆蓋問題的材料。並非所有代碼都如此簡單。 –
你比較過這兩個程序的IL嗎? – Oded
我確實比較了IL,但沒有看到任何可以幫我解釋的東西 – dmg