2009-05-26 7 views
33

密封タイプはなぜより速いのですか?密封タイプはなぜより速いのですか?

なぜこれが本当であるかについての詳細な情報が不思議です。

+2

参照:http://stackoverflow.com/questions/268251/why-seal-a-クラス – Shog9

+0

彼らですか?私はわかりません... CLRがメソッドディスパッチテーブルを最適化することができ、それがもはや成長できないことを知ることができます。 – harpo

+1

@harpo:この参照を参照してください:http://msdn.microsoft.com/en-us/library/ms173150.aspx私はそれを私の答えに追加しましたが、単純な事実はWHYに関する多くのことを言っていないので、私はそれを追加しないことにしました... –

答えて

36

密閉されたクラスを使用すると、コンパイラは最小限の最適化を行うことができます。

密封されたクラスでメソッドを呼び出すときに、そのタイプがコンパイル時にその密閉されたクラスであると宣言された場合、コンパイラは(ほとんどの場合)呼び出しIL命令を使用してcallvirt IL命令。これは、メソッドのターゲットをオーバーライドできないためです。コールはヌルチェックを排除し、仮想テーブルをチェックする必要がないため、callvirtよりも速いvtableルックアップを行います。

これは、パフォーマンスに対して非常にわずかな改善です。

言われているように、私はクラスを封印するかどうかを決めるときは、それを完全に無視します。封印されたタイプをマークすることは、実際の設計決定ではなく、設計上の決定でなければなりません。あなたは、自分自身を含めて、今や将来、あなたのクラスから潜在的にサブクラス化したいと思っていますか?その場合は、封印しないでください。そうでない場合は、シールしてください。それが本当に決定的な要因になるはずです。

+4

将来のバージョンでのクラスの開封は、その逆が真ではない間は改行ではないため、明示的に拡張する必要のないパブリックパブリックの型に向けるのは良い考えです。 –

+1

@Neil Williams:私は同意します。一般に、クラスの開封は安全であり、密封はできないので、あなたが公共図書館を作っているのであれば、密封は良いことです。しかし、これにより、パフォーマンス上の問題よりも設計上の選択を密にすることができます。 –

+0

それはインライン化によるものだと思っていました。 C#コンパイラは、そのILコードのヌルチェックの副作用が好きなので、常にcallvirtを使用します。 –

9

本質的に、仮想関数テーブルへの拡張について心配する必要はありません。密封された型は拡張することができないため、ランタイムはそれらがどのように多態性になるか心配する必要はありません。

5

JITコンパイラが密封型を使用して仮想メソッドへの呼び出しを見た場合、メソッドを非仮想的に呼び出すことでより効率的なコードを生成できます。 vtableルックアップを実行する必要がないため、非仮想メソッドを呼び出す方が高速です。 IMHOこれは、アプリケーションのパフォーマンスを向上させるための最後の手段として使用されるマイクロ最適化です。あなたのメソッドがどんなコードを含んでいても、仮想バージョンは、コード自体を実行するコストと比較して、非仮想よりも無視できるほど遅くなります。

+2

なぜでしょうかこれは最後の手段ですか?なぜあなたのクラスをデフォルトで封印するだけではないのですか?これに関連して多少のコストがかかっている場合(通常、読みにくいコード、またはより多くの開発時間)、それは通常、マイクロ・フィジケーションとみなされます。それを行うことに欠点がない場合、パフォーマンスが問題であるかどうかにかかわらず、なぜそれをしないのですか? – jalf

+1

クラスをシールすると、継承の使用が防止されます。これにより、開発がより困難になり、特定のバグを回避することができます。理想的には、それについて考え、継承のために設計し、拡張され、他のすべてを封印するように設計されたものを明白にする。ブラインドシールはあまりにも厳しいです。 – Eddie

3

他の回答を拡張するには、密封されたクラス(Javaの最終クラスに相当)を拡張することはできません。これは、コンパイラがこのクラスのメソッドが使用されていると見なすたびに、コンパイラは実行時のディスパッチが必要ないことを絶対に知っています。階層内のどのクラスのどのメソッドを呼び出す必要があるかを動的に調べるためにクラスを調べる必要はありません。これは、ブランチを動的ではなくコンパイルできることを意味します。例えば

IメソッドmakeNoise()を有する非密封されたクラスAnimalを持っている場合、コンパイラは、必ずしも任意Animalインスタンスがそのメソッドをオーバーライドするかどうか分かりません。したがって、AnimalインスタンスがmakeNoise()を呼び出すたびに、インスタンスが拡張クラスでこのメソッドをオーバーライドするかどうかを確認するために、インスタンスのクラス階層をチェックする必要があります。

しかし、feedAnimal()メソッドを持つ密封クラスAnimalFeederがある場合、コンパイラはこのメソッドをオーバーライドできないことを確実に認識しています。仮想ディスパッチテーブルを使用するのではなく、サブルーチンまたは同等の命令への分岐でコンパイルできます。

注:あなたは、そのクラスから任意の継承を防ぐために、クラスにsealedを使用することができ、あなたはその方法のさらなる上書きを防ぐために、基本クラスでvirtualを宣言された方法でsealedを使用することができます。

8

C#コンパイラが "call" & "callvirt"命令を発行したときに示す小さなコードサンプルを投稿することを決定しました。

だから、ここで私が使用したすべての種類のソースコードは次のとおりです。

public sealed class SealedClass 
    { 
     public void DoSmth() 
     { } 
    } 

    public class ClassWithSealedMethod : ClassWithVirtualMethod 
    { 
     public sealed override void DoSmth() 
     { } 
    } 

    public class ClassWithVirtualMethod 
    { 
     public virtual void DoSmth() 
     { } 
    } 

また、私は、すべての呼び出しをひとつの方法「DoSmthを()」メソッドがあります:

public void Call() 
    { 
     SealedClass sc = new SealedClass(); 
     sc.DoSmth(); 

     ClassWithVirtualMethod cwcm = new ClassWithVirtualMethod(); 
     cwcm.DoSmth(); 

     ClassWithSealedMethod cwsm = new ClassWithSealedMethod(); 
     cwsm.DoSmth(); 
    } 

コール」を探しています(理論的に)C#コンパイラは2 "callvirt" & "call"命令を出すべきだと言うことができますか? は残念ながら、現実は少し異なっている - 3「callvirt」-s:

.method public hidebysig instance void Call() cil managed 
{ 
    .maxstack 1 
    .locals init (
     [0] class TestApp.SealedClasses.SealedClass sc, 
     [1] class TestApp.SealedClasses.ClassWithVirtualMethod cwcm, 
     [2] class TestApp.SealedClasses.ClassWithSealedMethod cwsm) 
    L_0000: newobj instance void TestApp.SealedClasses.SealedClass::.ctor() 
    L_0005: stloc.0 
    L_0006: ldloc.0 
    L_0007: callvirt instance void TestApp.SealedClasses.SealedClass::DoSmth() 
    L_000c: newobj instance void TestApp.SealedClasses.ClassWithVirtualMethod::.ctor() 
    L_0011: stloc.1 
    L_0012: ldloc.1 
    L_0013: callvirt instance void TestApp.SealedClasses.ClassWithVirtualMethod::DoSmth() 
    L_0018: newobj instance void TestApp.SealedClasses.ClassWithSealedMethod::.ctor() 
    L_001d: stloc.2 
    L_001e: ldloc.2 
    L_001f: callvirt instance void TestApp.SealedClasses.ClassWithVirtualMethod::DoSmth() 
    L_0024: ret 
} 

理由は非常に簡単です:タイプのインスタンスは、「DoSmth()」メソッドを呼び出す前に、ヌルに等しくない場合、実行時に確認する必要があります。 しかし、我々はまだC#コンパイラが最適化されたILコードを放出することができるだろうというような方法で私たちのコードを書くことができます

public void Call() 
    { 
     new SealedClass().DoSmth(); 

     new ClassWithVirtualMethod().DoSmth(); 

     new ClassWithSealedMethod().DoSmth(); 
    } 

結果は次のとおりです。

.method public hidebysig instance void Call() cil managed 
{ 
    .maxstack 8 
    L_0000: newobj instance void TestApp.SealedClasses.SealedClass::.ctor() 
    L_0005: call instance void TestApp.SealedClasses.SealedClass::DoSmth() 
    L_000a: newobj instance void TestApp.SealedClasses.ClassWithVirtualMethod::.ctor() 
    L_000f: callvirt instance void TestApp.SealedClasses.ClassWithVirtualMethod::DoSmth() 
    L_0014: newobj instance void TestApp.SealedClasses.ClassWithSealedMethod::.ctor() 
    L_0019: callvirt instance void TestApp.SealedClasses.ClassWithVirtualMethod::DoSmth() 
    L_001e: ret 
} 

あなたが非を呼び出そうあなたが "callvirt"の代わりに "call"命令を得るのと同じ方法で非密閉クラスの仮想メソッド

+0

2番目の例でヌルチェックを避ける理由はありますか?説明していただけますか? –

+1

最初の例のように "SealedClass"型のローカル変数を使用しないので、コンパイラは 'null'かどうかを確認する必要はありません。 "SealedClass.DoSmth()"メソッドをstaticとして宣言すると、同じILコードが生成されます –

関連する問題