次のコードをテストの参照として使用して、Ildasm.exe(IL逆アセンブラー)ツールを使用してコンパイラーによって生成されたMicrosoft中間言語(MSIL)情報を分析してみましょう。
public sealed class Sealed
{
public string Message { get; set; }
public void DoStuff() { }
}
public class Derived : Base
{
public sealed override void DoStuff() { }
}
public class Base
{
public string Message { get; set; }
public virtual void DoStuff() { }
}
static void Main()
{
Sealed sealedClass = new Sealed();
sealedClass.DoStuff();
Derived derivedClass = new Derived();
derivedClass.DoStuff();
Base BaseClass = new Base();
BaseClass.DoStuff();
}
このツールを実行するには、Visual Studioの開発者コマンドプロンプトを開き、コマンドildasmを実行します。
**********************************************************************
** Visual Studio 2017 Developer Command Prompt v15.9.13
** Copyright (c) 2017 Microsoft Corporation
**********************************************************************
C:\Program Files (x86)\Microsoft Visual Studio\2017\Community>ildasm
アプリケーションが起動したら、前のアプリケーションの実行可能ファイル(またはアセンブリ)をロードします
この画像に代替テキストはありませんMainメソッドをダブルクリックして、Microsoft中間言語(MSIL)情報を表示します。
.method private hidebysig static void Main() cil managed
{
.entrypoint
// Code size 41 (0x29)
.maxstack 8
IL_0000: newobj instance void ConsoleApp1.Program/Sealed::.ctor()
IL_0005: callvirt instance void ConsoleApp1.Program/Sealed::DoStuff()
IL_000a: newobj instance void ConsoleApp1.Program/Derived::.ctor()
IL_000f: callvirt instance void ConsoleApp1.Program/Base::DoStuff()
IL_0014: newobj instance void ConsoleApp1.Program/Base::.ctor()
IL_0019: callvirt instance void ConsoleApp1.Program/Base::DoStuff()
IL_0028: ret
} // end of method Program::Main
ご覧のとおり、各クラスはnewobjを使用して、オブジェクト参照をスタックにプッシュすることで新しいインスタンスを作成し、 callvirtはそれぞれのオブジェクトのDoStuff()メソッドのレイトバウンドを呼び出します。
この情報から判断すると、封印されたクラス、派生クラス、および基本クラスの両方が、コンパイラーによって同じ方法で管理されているようです。念のため、VisualStudioの逆アセンブリウィンドウを使用してJITでコンパイルされたコードを分析してさらに詳しく見ていきましょう。
[ツール]>[オプション]>[デバッグ]>[一般]で、[アドレスレベルのデバッグを有効にする]を選択して逆アセンブリを有効にします。
この画像に代替テキストはありませんアプリケーションの開始時にブレーキポイントを設定し、デバッグを開始します。アプリケーションがブレーキポイントに達したら、[デバッグ]>[ウィンドウ]>[逆アセンブリ]を選択して、[逆アセンブリ]ウィンドウを開きます。
--- C:\Users\Ivan Porta\source\repos\ConsoleApp1\Program.cs --------------------
{
0066084A in al,dx
0066084B push edi
0066084C push esi
0066084D push ebx
0066084E sub esp,4Ch
00660851 lea edi,[ebp-58h]
00660854 mov ecx,13h
00660859 xor eax,eax
0066085B rep stos dword ptr es:[edi]
0066085D cmp dword ptr ds:[5842F0h],0
00660864 je 0066086B
00660866 call 744CFAD0
0066086B xor edx,edx
0066086D mov dword ptr [ebp-3Ch],edx
00660870 xor edx,edx
00660872 mov dword ptr [ebp-48h],edx
00660875 xor edx,edx
00660877 mov dword ptr [ebp-44h],edx
0066087A xor edx,edx
0066087C mov dword ptr [ebp-40h],edx
0066087F nop
Sealed sealedClass = new Sealed();
00660880 mov ecx,584E1Ch
00660885 call 005730F4
0066088A mov dword ptr [ebp-4Ch],eax
0066088D mov ecx,dword ptr [ebp-4Ch]
00660890 call 00660468
00660895 mov eax,dword ptr [ebp-4Ch]
00660898 mov dword ptr [ebp-3Ch],eax
sealedClass.DoStuff();
0066089B mov ecx,dword ptr [ebp-3Ch]
0066089E cmp dword ptr [ecx],ecx
006608A0 call 00660460
006608A5 nop
Derived derivedClass = new Derived();
006608A6 mov ecx,584F3Ch
006608AB call 005730F4
006608B0 mov dword ptr [ebp-50h],eax
006608B3 mov ecx,dword ptr [ebp-50h]
006608B6 call 006604A8
006608BB mov eax,dword ptr [ebp-50h]
006608BE mov dword ptr [ebp-40h],eax
derivedClass.DoStuff();
006608C1 mov ecx,dword ptr [ebp-40h]
006608C4 mov eax,dword ptr [ecx]
006608C6 mov eax,dword ptr [eax+28h]
006608C9 call dword ptr [eax+10h]
006608CC nop
Base BaseClass = new Base();
006608CD mov ecx,584EC0h
006608D2 call 005730F4
006608D7 mov dword ptr [ebp-54h],eax
006608DA mov ecx,dword ptr [ebp-54h]
006608DD call 00660490
006608E2 mov eax,dword ptr [ebp-54h]
006608E5 mov dword ptr [ebp-44h],eax
BaseClass.DoStuff();
006608E8 mov ecx,dword ptr [ebp-44h]
006608EB mov eax,dword ptr [ecx]
006608ED mov eax,dword ptr [eax+28h]
006608F0 call dword ptr [eax+10h]
006608F3 nop
}
0066091A nop
0066091B lea esp,[ebp-0Ch]
0066091E pop ebx
0066091F pop esi
00660920 pop edi
00660921 pop ebp
00660922 ret
前のコードでわかるように、オブジェクトの作成は同じですが、封印されたクラスと派生クラス/基本クラスのメソッドを呼び出すために実行される命令は少し異なります。データをRAMのレジスタに移動した後(mov命令)、封印されたメソッドを呼び出し、実際にメソッドを呼び出す前に、dword ptr [ecx]とecx(cmp命令)の比較を実行します。
Torbj¨ornGranlundが作成したレポートによると、AMDおよびIntel x86プロセッサの命令レイテンシとスループットによると、IntelPentium4での次の命令の速度は次のとおりです。
- mov:レイテンシとして1サイクルあり、プロセッサはこのタイプのサイクルごとに2.5命令を維持できます
- cmp:レイテンシとして1サイクルあり、プロセッサはこのタイプのサイクルごとに2つの命令を維持できます
結論として、今日のコンパイラとプロセッサの最適化により、封印されたクラスと封印されていないクラスの間のパフォーマンスは基本的に非常に少なくなり、ほとんどのアプリケーションとは無関係になりました。
参考文献