EricPetroeljeの答えを拡張します。
プログラムを次のように書き直すと(動作は同じですが、ラムダ関数を回避すると分解が読みやすくなります)、プログラムを分解して、「フィールドの値をレジスタにキャッシュする」ことの実際の意味を確認できます。
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はwhile(!foo.Complete)を次のコードにコンパイルします。
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
最後の2行が問題です。eaxがゼロの場合、eaxの値を変更するコードがなくても、「EAXはゼロですか?」という無限ループになります。
Completeはプロパティです:
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はプロパティゲッターをインライン化して(そうでない場合は、他の関数にいくつかの命令が送信されるのを確認します)、フィールドを直接call
読み取るためのいくつかの単純なコードにインライン化しますが、変数をキャッシュすることは許可されていないため、ループを生成するときに繰り返し読み取りますComplete
レジスタを無意味に読み取るのではなく、メモリを何度も繰り返します
x64リリースでは、64ビットCLR JITはwhile(!foo.Complete)をこのコードにコンパイルします
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ビットのCLRのようにフィールド値をレジスタにキャッシュすることを許可されているかどうかはわかりませんが、キャッシュできる場合は、そうする必要はありません。おそらくそれは将来的になりますか?
いずれにせよ、これは、動作がプラットフォームに依存し、変更される可能性があることを示しています。お役に立てば幸いです:-)