Frage

Ich haben eine Menge Tipps zur Optimierung, die sagen, dass Sie markieren Sie Ihre Klassen als versiegelt, um zusätzliche performance-Vorteile.

Ich lief einige tests, um zu überprüfen die Leistung differential-und keine gefunden.Mache ich etwas falsch?Bin ich fehlt, der Fall, wo versiegelte Klassen geben wird, bessere Ergebnisse?

Hat jemand tests ausführen und sehen einen Unterschied?

Hilf mir zu lernen :)

War es hilfreich?

Lösung

Der JITter wird manchmal die Verwendung von nicht-virtuellen Methoden aufrufen, die in verschlossenen Klassen, da gibt es keine Möglichkeit, Sie kann weiter ausgebaut werden.

Es gibt komplexe Regeln zur Berufung geben, virtuelle/nicht virtuelle, und ich kenne Sie nicht alle, also kann ich nicht wirklich skizzieren Sie für Sie, aber wenn Sie google für versiegelte Klassen und virtuelle Methoden, die Sie möglicherweise finden Sie einige Artikel zum Thema.

Beachten Sie, dass jede Art von Leistung, die Sie erhalten würde, von dieser Stufe der Optimierung betrachtet werden sollte als in letzter Instanz immer die Optimierung auf der algorithmischen Ebene, bevor Sie die Optimierung auf der code-Ebene.

Hier ist ein link, erwähnen Sie diese: Wandern auf der sealed-Schlüsselwort

Andere Tipps

Die Antwort ist Nein, versiegelte Klassen nicht besser als nicht-versiegelt.

Das Problem kommt auf die call vs callvirt IL op-codes. Call schneller als callvirt, und callvirt wird hauptsächlich verwendet, wenn Sie nicht wissen, ob die Objekt wurde Unterklassen.So können die Leute davon ausgehen, dass wenn Sie das Siegel eine Klasse für die op-codes ändern von calvirts zu calls und schneller ist.

Leider callvirt andere Dinge, die machen es nützlich zu, wie null-Referenzen.Dies bedeutet, dass selbst wenn eine Klasse ist versiegelt, die Referenz vielleicht noch null sein und somit eine callvirt erforderlich ist.Sie können dies umgehen (ohne Dichtung der Klasse), aber es wird ein bisschen sinnlos.

Structs verwenden call weil Sie sich nicht untergeordnet sind und nie null.

Siehe diese Frage für weitere Informationen:

Rufen Sie und callvirt

Update:Als der .NET Core 2.0 und .NET Desktop 4.7.1, die CLR unterstützt jetzt devirtualization.Es kann nehmen Methoden in versiegelten Klassen und ersetzen virtuelle Anrufe mit direkten anrufen - und Sie können dies auch für nicht versiegelte Klassen, wenn es herausfinden kann, ist es sicher, dies zu tun.

In einem solchen Fall (eine versiegelte Klasse, die die CLR konnte nicht anders erkennen, als sicher zu devirtualise), eine versiegelte Klasse sollte eigentlich bieten eine Art von performance-Vorteil.

Das heißt, ich würde nicht denken, es wäre Wert, sich Gedanken über es sei denn, Sie hatte bereits profilierten sich die code und festgestellt, dass Sie in einer besonders heißen Pfad aufgerufen Millionen mal, oder so etwas wie, dass:

https://blogs.msdn.microsoft.com/dotnet/2017/06/29/performance-improvements-in-ryujit-in-net-core-and-net-framework/


Ursprüngliche Antwort:

Ich habe die folgende test-Programm, und dann dekompiliert es mit dem Reflektor zu sehen, was MSIL-code ausgesendet wurde.

public class NormalClass {
    public void WriteIt(string x) {
        Console.WriteLine("NormalClass");
        Console.WriteLine(x);
    }
}

public sealed class SealedClass {
    public void WriteIt(string x) {
        Console.WriteLine("SealedClass");
        Console.WriteLine(x);
    }
}

public static void CallNormal() {
    var n = new NormalClass();
    n.WriteIt("a string");
}

public static void CallSealed() {
    var n = new SealedClass();
    n.WriteIt("a string");
}

In allen Fällen ist der C# - compiler (Visual studio 2010 in der Release-build-Konfiguration) wird identisch MSIL, die ist wie folgt:

L_0000: newobj instance void <NormalClass or SealedClass>::.ctor()
L_0005: stloc.0 
L_0006: ldloc.0 
L_0007: ldstr "a string"
L_000c: callvirt instance void <NormalClass or SealedClass>::WriteIt(string)
L_0011: ret 

Die oft zitierte Grund, dass die Menschen sagen sealed bietet performance-Vorteile ist, dass der compiler kennt die Klasse nicht überschrieben und kann daher nicht mit dem call statt callvirt da Sie nicht haben, um zu überprüfen, für virtuals, etc.Als bewährte oben, das ist nicht wahr.

Mein Nächster Gedanke war, dass, obwohl die MSIL ist identisch, vielleicht auch der JIT-compiler behandelt versiegelte Klassen anders?

Ich lief ein release build unter dem visual studio-debugger, und betrachteten den dekompiliert x86-Ausgabe.In beiden Fällen wird die x86-code war identisch, mit der Ausnahme von Klassennamen und die Funktion von Speicher-Adressen (die muss natürlich anders sein).Hier ist es

//            var n = new NormalClass();
00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  sub         esp,8 
00000006  cmp         dword ptr ds:[00585314h],0 
0000000d  je          00000014 
0000000f  call        70032C33 
00000014  xor         edx,edx 
00000016  mov         dword ptr [ebp-4],edx 
00000019  mov         ecx,588230h 
0000001e  call        FFEEEBC0 
00000023  mov         dword ptr [ebp-8],eax 
00000026  mov         ecx,dword ptr [ebp-8] 
00000029  call        dword ptr ds:[00588260h] 
0000002f  mov         eax,dword ptr [ebp-8] 
00000032  mov         dword ptr [ebp-4],eax 
//            n.WriteIt("a string");
00000035  mov         edx,dword ptr ds:[033220DCh] 
0000003b  mov         ecx,dword ptr [ebp-4] 
0000003e  cmp         dword ptr [ecx],ecx 
00000040  call        dword ptr ds:[0058827Ch] 
//        }
00000046  nop 
00000047  mov         esp,ebp 
00000049  pop         ebp 
0000004a  ret 

Ich dachte dann, dass vielleicht unter den debugger bewirkt, dass es zu weniger aggressiven Optimierung?

Ich lief dann eine standalone-release build ausführbaren Datei außerhalb der debugging-Umgebungen, und verwendet WinDBG + SOS zu brechen, nach dem Programm abgeschlossen hatten und die Ansicht des dissasembly des JIT-kompiliert für x86-code.

Wie Sie sehen können aus dem folgenden code wird beim ausführen außerhalb des Debuggers der JIT-compiler ist mehr aggressiv, und es hat inlined der WriteIt Methode gerade in der Anrufer.Der entscheidende Punkt ist jedoch, dass es identisch war beim Aufruf einer versiegelten vs nicht versiegelte Klasse.Es gibt keinerlei Unterschied zwischen einem geschlossenen oder nonsealed Klasse.

Hier ist es bei dem Aufruf einer normalen Klasse:

Normal JIT generated code
Begin 003c00b0, size 39
003c00b0 55              push    ebp
003c00b1 8bec            mov     ebp,esp
003c00b3 b994391800      mov     ecx,183994h (MT: ScratchConsoleApplicationFX4.NormalClass)
003c00b8 e8631fdbff      call    00172020 (JitHelp: CORINFO_HELP_NEWSFAST)
003c00bd e80e70106f      call    mscorlib_ni+0x2570d0 (6f4c70d0) (System.Console.get_Out(), mdToken: 060008fd)
003c00c2 8bc8            mov     ecx,eax
003c00c4 8b1530203003    mov     edx,dword ptr ds:[3302030h] ("NormalClass")
003c00ca 8b01            mov     eax,dword ptr [ecx]
003c00cc 8b403c          mov     eax,dword ptr [eax+3Ch]
003c00cf ff5010          call    dword ptr [eax+10h]
003c00d2 e8f96f106f      call    mscorlib_ni+0x2570d0 (6f4c70d0) (System.Console.get_Out(), mdToken: 060008fd)
003c00d7 8bc8            mov     ecx,eax
003c00d9 8b1534203003    mov     edx,dword ptr ds:[3302034h] ("a string")
003c00df 8b01            mov     eax,dword ptr [ecx]
003c00e1 8b403c          mov     eax,dword ptr [eax+3Ch]
003c00e4 ff5010          call    dword ptr [eax+10h]
003c00e7 5d              pop     ebp
003c00e8 c3              ret

Vs eine versiegelte Klasse:

Normal JIT generated code
Begin 003c0100, size 39
003c0100 55              push    ebp
003c0101 8bec            mov     ebp,esp
003c0103 b90c3a1800      mov     ecx,183A0Ch (MT: ScratchConsoleApplicationFX4.SealedClass)
003c0108 e8131fdbff      call    00172020 (JitHelp: CORINFO_HELP_NEWSFAST)
003c010d e8be6f106f      call    mscorlib_ni+0x2570d0 (6f4c70d0) (System.Console.get_Out(), mdToken: 060008fd)
003c0112 8bc8            mov     ecx,eax
003c0114 8b1538203003    mov     edx,dword ptr ds:[3302038h] ("SealedClass")
003c011a 8b01            mov     eax,dword ptr [ecx]
003c011c 8b403c          mov     eax,dword ptr [eax+3Ch]
003c011f ff5010          call    dword ptr [eax+10h]
003c0122 e8a96f106f      call    mscorlib_ni+0x2570d0 (6f4c70d0) (System.Console.get_Out(), mdToken: 060008fd)
003c0127 8bc8            mov     ecx,eax
003c0129 8b1534203003    mov     edx,dword ptr ds:[3302034h] ("a string")
003c012f 8b01            mov     eax,dword ptr [ecx]
003c0131 8b403c          mov     eax,dword ptr [eax+3Ch]
003c0134 ff5010          call    dword ptr [eax+10h]
003c0137 5d              pop     ebp
003c0138 c3              ret

Für mich ist dieses bietet solide Beweis dafür, dass es nicht jede Leistungssteigerung zwischen den aufrufen von Methoden auf versiegelten vs nicht versiegelte Klassen...Ich glaube, ich bin jetzt glücklich :-)

Wie ich weiß, gibt es keine Garantie der Leistung profitieren.Aber es gibt eine chance, um die Leistung verringern Strafe unter einem bestimmten Zustand mit geschlossenen Methode.(versiegelte Klasse macht Methoden abgedichtet werden.)

Aber es ist bis zu compiler-Implementierung und Durchführung Umwelt.


Details

Viele moderne CPUs verwenden Sie lange pipeline-Struktur, um die Leistung zu erhöhen.Da die CPU ist unheimlich schneller als der Speicher, CPU, prefetch-code aus dem Speicher zu beschleunigen pipeline.Wenn die code ist nicht bereit, zu richtiger Zeit, die pipelines im Leerlauf.

Es ist ein großes Hindernis genannt dynamic dispatch was stört dieses "prefetching' Optimierung.Sie können dies verstehen, nur als eine bedingte Verzweigung.

// Value of `v` is unknown,
// and can be resolved only at runtime.
// CPU cannot know which code to prefetch.
// Therefore, just prefetch any one of a() or b().
// This is *speculative execution*.
int v = random();
if (v==1) a();
else b();

Die CPU kann nicht prefetch-next-code ausführen, in diesem Fall, weil der code-position ist unbekannt, bis der Zustand behoben ist.So macht das Gefahr Ursachen pipeline-Leerlauf.Und Leistungseinbußen, die durch im Leerlauf ist riesig in regelmäßigen.

Ähnliches passiert bei der Methode überschreiben.Compiler kann bestimmen, die geeignete Methode überschreiben für die aktuelle Methode aufrufen, aber manchmal ist es unmöglich.In diesem Fall, die richtige Methode bestimmt erst zur Laufzeit.Dies ist auch ein Fall von dynamischen Versand, und einer der Hauptgründe, dynamisch typisierte Sprachen sind in der Regel langsamer als bei statisch-typisierten Sprachen.

Einige CPU (einschließlich der neuesten Intel x86-chips) verwendet eine Technik namens spekulative Ausführung nutzen pipeline auch auf die situation.Nur prefetch eines der Ausführung Weg.Aber die Trefferquote ist diese Technik nicht so hoch.Und die Spekulation führt dazu, dass pipeline-stall-die macht auch große performance-Einbußen.(dieser ist komplett von der CPU-Implementierung.einige mobile CPU ist bekannt, wie nicht diese Art der Optimierung, um Energie zu sparen)

Im Grunde, C# ist eine statisch kompilierte Sprache.Aber nicht immer.Ich weiß nicht, die genaue Beschaffenheit und das ist völlig bis zu compiler-Implementierung.Einige Compiler können beseitigen die Möglichkeit der dynamischen Versand durch Verhinderung Methode überschreiben, wenn die Methode, die als markiert ist sealed.Dumm Compiler kann nicht.Dies ist der performance-Vorteil der sealed.


Diese Antwort (Warum ist es schneller, um Prozess ein sortiertes array als ein Unsortiertes array?) beschreibt die branch prediction viel besser.

Kennzeichnung einer Klasse sealed sollte keine Auswirkung auf die Leistung.

Es gibt Fälle, in denen csc möglicherweise müssen emittieren eine callvirt opcode statt einer call opcode.Es scheint jedoch, solche Fälle sind selten.

Und es scheint mir, dass der JIT sollte in der Lage zu emittieren die gleiche nicht-virtuelle Funktion aufrufen, für callvirt dass es für call, wenn er weiß, dass die Klasse keine Unterklassen, die (noch).Wenn nur eine Implementierung der Methode vorhanden ist, gibt es keinen Punkt laden seine Adresse von einem vtable—einfach anrufen, die eine Umsetzung direkt.Für diese Angelegenheit, das JIT kann sogar inline-Funktion.

Es ist ein bisschen ein Glücksspiel, auf die JIT-Teil, denn wenn eine Unterklasse ist später geladen, wird der JIT-wegwerfen müssen, dass die Maschine code und kompilieren Sie den code erneut ein, und entsendet eine echte virtuelle call.Meine Vermutung ist, dies passiert aber nicht oft in der Praxis.

(Und ja, VM Designer wirklich aggressiv verfolgen, diese kleine Leistung gewinnt.)

Versiegelte Klassen sollte bieten eine performance-Verbesserung.Da eine versiegelte Klasse kann nicht abgeleitet werden, alle virtuellen Mitglieder können sich in nicht-virtuellen Mitglieder.

Natürlich, wir reden wirklich kleine Gewinne.Ich würde nicht markieren einer Klasse wie versiegelt, nur um eine Verbesserung der Leistung, es sei denn, profiling, offenbart es ein problem zu sein.

<off-topic-rant>

Ich verabscheuen versiegelte Klassen.Auch wenn die performance-Vorteile sind erstaunlich (was ich bezweifle), Sie zerstören das Objekt-orientierte Modell durch die Verhinderung der Wiederverwendung über die Vererbung.Für Beispiel, die Thread-Klasse ist versiegelt.Ich kann zwar sehen, dass man vielleicht möchten Sie threads, um so effizient wie möglich, ich kann mir auch vorstellen Szenarien, wo zu können Unterklasse Thread hätte große Vorteile.Klasse Autoren, wenn Sie muss versiegeln Sie Ihre Klassen für "performance" Gründen, bitte geben Sie eine Schnittstelle zumindest so wir don nicht haben zu wickeln-und-ersetzen-überall, wir brauchen eine Funktion, die Sie vergessen haben.

Beispiel: SafeThread musste wickeln Sie die Thread-Klasse, da Thread ist versiegelt und es gibt keine IThread-Schnittstelle;SafeThread automatisch fallen unbehandelte Ausnahmen in threads, etwas völlig fehlen, der Thread-Klasse.[und Nein, die unbehandelte Ausnahme-Ereignisse nicht pick-up-nicht behandelte Ausnahmen in sekundären threads].

</off-topic-rant>

Ich als "versiegelt" - Klassen, die normale Fall ist, und ich habe IMMER einen Grund für das auslassen der "sealed" - Schlüsselwort.

Die wichtigsten Gründe für mich sind:

a) Besser compile-Zeit überprüft (casting Schnittstellen nicht umgesetzt werden erkannt zur compile-Zeit, nicht erst zur Laufzeit)

und oben Grund:

b) Missbrauch von meinen Klassen ist nicht möglich, dass Weg

Ich wünschte, Microsoft hätte "versiegelt" ist der standard, kein "entsiegelt".

@Vaibhav, welche Art von tests haben Sie auszuführen, um die Leistung zu Messen?

Ich denke, man hätte zu verwenden Rotor und zum bohren in CLI und zu verstehen, wie eine versiegelte Klasse würde die Leistung zu verbessern.

SSCLI (Rotor)
SSCLI:Shared Source Common Language Infrastructure

Die Common Language Infrastructure (CLI) ist die ECMA-standard, der beschreibt den Kern des .NET Rahmen.Die Shared-Source-CLI (SSCLI), auch bekannt als Rotor, ist ein komprimierte Archiv der Quelltexte code um eine funktionierende Umsetzung der ECMA CLI und die ECMA C# - Sprache Spezifikation technologies Herz von Microsoft .NET Architektur.

versiegelte Klassen zumindest ein kleines bisschen schneller, aber manchmal kann waayyy schneller...wenn das JIT-Optimierer können die inline-Anrufe, die würde haben sonst worden virtuelle Anrufe.Also, wo es oft genannte Methoden, die klein genug sind, um inlined, auf jeden Fall prüfen, Abdichten der Klasse.

Aber der beste Grund zum versiegeln einer Klasse ist, zu sagen: "ich habe nicht design-diese werden vererbt, so werde ich nicht zu lassen, Sie bekommen verbrannt, vorausgesetzt, es wurde entwickelt, um so zu sein, und ich werde nicht verbrennen, mich von immer gesperrt in eine Implementierung, weil ich dich daraus."

Ich weiß, dass einige hier gesagt haben, Sie hassen, versiegelte Klassen, weil Sie wollen, die Gelegenheit, eine Ableitung von etwas...aber das ist OFT nicht die wartbar Wahl...da das aussetzen einer Klasse zur Ableitung sperrt Sie in einen viel mehr, als nicht setzen alle, dass.Dieser ist ähnlich wie zu sagen: "ich verabscheue Klassen, die private Mitglieder...Ich habe oft können nicht machen die Klasse machen, was ich will, weil ich keinen Zugriff habe." Kapselung ist wichtig,...Dichtung ist eine form der Kapselung.

Führen Sie diesen code und Sie werden sehen, dass die versiegelte Klassen sind 2-mal schneller:

class Program
{
    static void Main(string[] args)
    {
        Console.ReadLine();

        var watch = new Stopwatch();
        watch.Start();
        for (int i = 0; i < 10000000; i++)
        {
            new SealedClass().GetName();
        }
        watch.Stop();
        Console.WriteLine("Sealed class : {0}", watch.Elapsed.ToString());

        watch.Start();
        for (int i = 0; i < 10000000; i++)
        {
            new NonSealedClass().GetName();
        }
        watch.Stop();
        Console.WriteLine("NonSealed class : {0}", watch.Elapsed.ToString());

        Console.ReadKey();
    }
}

sealed class SealedClass
{
    public string GetName()
    {
        return "SealedClass";
    }
}

class NonSealedClass
{
    public string GetName()
    {
        return "NonSealedClass";
    }
}

Ausgabe:Versiegelte Klasse :00:00:00.1897568 NonSealed-Klasse :00:00:00.3826678

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top