Czy zamknięte zajęcia naprawdę oferują wzrost wydajności?

głosy
118

Mam natknąć wiele wskazówek optymalizacyjnych, które mówią, że należy oznaczyć swoje klasy jako uszczelnione, aby uzyskać dodatkowe korzyści z wydajnością.

Pobiegłem kilka testów, aby sprawdzić różnicę wydajności i znaleźli. czy robię coś źle? Jestem brakuje przypadek, w którym zamknięte zajęcia dadzą lepsze rezultaty?

Czy ktoś uruchomić testy i widać różnicę?

Pomoże mi nauczyć :)

Utwórz 05/08/2008 o 13:00
źródło użytkownik
W innych językach...                            


11 odpowiedzi

głosy
133

Odpowiedź brzmi nie, zamknięte zajęcia nie działają lepiej niż nie-uszczelnione.

Problem sprowadza się do callvs callvirtIL kody op. Calljest szybsza niż callvirt, a callvirtprzede wszystkim jest wykorzystywany, gdy nie wiem, czy obiekt został podklasy. Więc ludzie zakładają, że jeśli uszczelnić klasę wszystkie kody op zmieni od calvirtscelu callsi będzie szybciej.

Niestety callvirtrobi inne rzeczy, które sprawiają, że warto też, jak sprawdzanie referencji null. Oznacza to, że nawet jeśli klasa jest uszczelniony, odniesienie może wciąż być zerowa, a zatem callvirtjest potrzebne. Można obejść ten problem (bez konieczności uszczelniania klasę), ale staje się nieco bezcelowe.

Kodowanym używać call, ponieważ nie mogą być podklasy i nigdy nie są puste.

Zobacz to pytanie o więcej informacji:

Zadzwoń i callvirt

Odpowiedział 24/12/2008 o 03:09
źródło użytkownik

głosy
46

Jitter będzie czasami użyć połączenia non-wirtualnych metod w zamkniętych klas, ponieważ nie ma sposobu, mogą zostać rozszerzony.

Są to skomplikowane przepisy dotyczące rodzaju wywołującego, virtual / nonvirtual, a ja nie wiem, ich wszystkich, więc nie mogę naprawdę nakreślić je dla ciebie, ale jeśli google dla zamkniętych klas i metod wirtualnych można znaleźć kilka artykułów na ten temat.

Należy pamiętać, że każdy rodzaj korzyści wydajności będzie można uzyskać z tego poziomu optymalizacji powinny być traktowane jako ostatniej instancji, zawsze optymalizacji na poziomie algorytmicznych przed optymalizację na poziomie kodu.

Oto jeden link wspomnieć to: Wędrując na zamkniętej hasła

Odpowiedział 05/08/2008 o 13:32
źródło użytkownik

głosy
20

Z tego co wiem, nie ma gwarancji, z korzyścią dla wydajności. Ale jest szansa, aby zmniejszyć karę wydajności pod jakimś konkretnym stanie metodą zamkniętej. (zamknięte klasy sprawia, że wszystkie metody są uszczelnione).

Ale to zależy od implementacji kompilatora i środowiska wykonawczego.


Detale

Wiele nowoczesnych procesorów używać długo strukturę rurociągu w celu zwiększenia wydajności. Ponieważ procesor jest niewiarygodnie szybciej niż pamięć, procesor musi wstępne pobieranie kodu z pamięci, aby przyspieszyć rurociągu. Jeżeli kod nie jest gotowy w odpowiednim czasie, rurociąg będzie bezczynny.

Istnieje duża przeszkoda nazywana dynamiczny wysyłkowy który zakłóca to „” prefetching optymalizacji. Można to zrozumieć tylko jako warunkowego rozgałęzienia.

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

CPU nie może wstępne pobieranie następny kod do wykonania w tej sprawie, ponieważ następna pozycja kod jest nieznany aż warunek zostanie rozwiązany. Więc to sprawia, że zagrożenie powoduje rurociągu bezczynny. I kara występ biegu jałowym jest ogromna w regularnych.

Podobnie rzecz się zdarzyć w przypadku metody nadrzędnego. Kompilator może ustalić właściwą metodę nadpisywanie dla aktualnego wywołania metody, ale czasami jest to niemożliwe. W takim przypadku właściwa metoda może być określona tylko w czasie wykonywania. Jest to również przypadek dynamicznego wysyłki, a głównym powodem dynamicznie typowanych języków są zazwyczaj wolniejsze niż statycznie typowanych języków.

Niektóre CPU (w tym niedawny chipów Intela x86) wykorzystuje technikę zwaną wykonywanie spekulatywne wykorzystanie rurociągu nawet od sytuacji. Tylko wstępne pobieranie jedną ścieżkę realizacji. Ale trafień tej techniki nie jest tak wysoka. I awaria rurociągu powoduje spekulacje stoisko, które również sprawia ogromną karę wydajności. (jest to całkowicie za realizację procesora. Niektóre telefony CPU jest znany jako nie robi tego rodzaju optymalizacji w celu oszczędzania energii)

Zasadniczo, C # jest statycznie skompilowany języka. Ale nie zawsze. Nie wiem dokładnie stan i to jest całkowicie do realizacji kompilatora. Niektóre kompilatory może wyeliminować możliwość dynamicznego wysłania przez zapobieganie metody nadpisywanie, czy metoda jest oznaczona jako sealed. Głupie kompilatory nie może. Jest to korzyść wydajność z sealed.


Ta odpowiedź ( Dlaczego to szybciej przetwarzać posortowaną tablicę niż niesegregowanych tablicy? To jest opisujące przewidywania rozgałęzień dużo lepiej).

Odpowiedział 03/02/2011 o 16:22
źródło użytkownik

głosy
18

Aktualizacja: Jak z .NET 2.0 i .NET Rdzenia Pulpit 4.7.1, CLR obsługuje teraz devirtualization. To może trwać metod w zamkniętych klasach i zastąpienie wirtualnych połączeń z połączeń bezpośrednich - i może to zrobić także na zajęcia spoza zamkniętych, jeżeli można dowiedzieć się, że to bezpieczne, aby to zrobić.

W takim przypadku (uszczelniona klasa CLR nie mogłyby wykryć jako bezpieczne devirtualise), uszczelniona klasa powinna faktycznie oferują jakąś korzyść wydajności.

Powiedział, że nie myślę, że warto się martwić o ile nie jesteś już profilowane kod i ustalił, że byłaś w szczególnie gorącej ścieżce miano miliony razy, czy coś takiego:

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


Oryginalny Odpowiedź:

Zrobiłem następujący program testowy, a następnie dekompilować go przy użyciu reflektor, aby zobaczyć, co kod MSIL był emitowany.

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");
}

We wszystkich przypadkach, kompilator C # (Visual Studio 2010 w konfiguracji kompilacji Release) emituje identyczną MSIL, który przedstawia się następująco:

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 

Oft cytowany powodem, że ludzie mówią, uszczelnione zapewnia wzrost wydajności jest to, że kompilator wie, że klasa nie jest nadpisane, a więc można używać callzamiast callvirt, jak to nie musi sprawdzać virtualach itp Jak udowodniono powyżej, to nie jest prawdziwe.

Moja następna myśl była, że ​​choć MSIL jest identyczna, może kompilator JIT traktuje inaczej zamknięte zajęcia?

Pobiegłem kompilacji uwolnienia pod Visual Studio debugger i oglądał dekompilowana wyjście x86. W obu przypadkach kod x 86 były identyczne, z wyjątkiem Klasy i adresy pamięci funkcji (oczywiście musi być inny). Oto ona

//            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 

Pomyślałem wtedy być może działa pod debugger powoduje, że do wykonywania mniej agresywną optymalizację?

Potem prowadził samodzielną uwolnienie zbudować wykonywalny poza jakimkolwiek środowisku debugowania i wykorzystywane WinDBG + SOS włamać się po program zakończył, a także wyświetlać dissasembly JIT skompilowany kod x86.

Jak widać z poniższego kodu, gdy działa poza debugera kompilator JIT jest bardziej agresywny, i to inlined ten WriteItsposób prosto do rozmówcy. Jednak kluczową rzeczą jest to, że był identyczny Dzwoniąc uszczelnioną vs niezgrzewane klasie. Nie ma żadnej różnicy między zamkniętej lub nonsealed klasie.

Tutaj jest to podczas wywoływania normalną klasę:

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 zamkniętej klasie:

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

Dla mnie ten zapewnia solidne dowody, że nie może być żadnej poprawy wydajności pomiędzy wywoływanie metod na zamkniętych vs klas niewyspecjalizowanych uszczelnione ... Myślę, że teraz jestem zadowolony :-)

Odpowiedział 20/02/2012 o 21:45
źródło użytkownik

głosy
4

Znakowanie klasy sealednie powinno mieć wpływ na wydajność.

Istnieją przypadki, w których cscmogą mieć do emitowania callvirtopcodu zamiast callopcode. Wydaje się jednak, te przypadki są rzadkie.

I wydaje mi się, że JIT powinien być w stanie emitować taką samą wywołanie funkcji non-wirtualną callvirt, że będzie za call, jeśli wie, że klasa nie ma żadnych podklasy (jeszcze). Jeśli tylko jedna realizacja sposobu istnieje, nie ma sensu ładowania jego adresu z vtable-po prostu zadzwonić do jednej z implementacji bezpośrednio. W tej materii, JIT może nawet inline funkcję.

Jest to trochę ryzykowne ze strony JIT, ponieważ jeśli podklasą jest później załadowany, JIT będzie musiał wyrzucić ten kod maszynowy i ponownie skompilować kod, emitując prawdziwe wirtualne połączenia. Domyślam się to nie zdarza się często w praktyce.

(I tak, projektanci VM naprawdę agresywnie realizować te malutkie wygrywa wydajności).

Odpowiedział 20/11/2009 o 10:53
źródło użytkownik

głosy
3

I za „zamknięte” Zajęcia normalny przypadek i zawsze mają powód, aby pominąć „zapieczętowane” słowo kluczowe.

Najważniejsze powody, dla mnie są:

a) Lepsze kompilacji kontroli czasu (odlewanie do interfejsów nie wdrożone zostaną wykryte w czasie kompilacji, a nie tylko przy starcie)

a top powód:

b) naruszenie moich klas nie jest możliwe, że droga

Życzę Microsoft uczyniłby „uszczelnione” standard, a nie „niezamkniętych szczelnie”.

Odpowiedział 03/08/2010 o 15:27
źródło użytkownik

głosy
3

<Off-topic-rant>

Ja nienawidzę zapieczętowanych klas. Nawet jeżeli świadczenia są zdumiewające osiągi (w co wątpię), to zniszczyć modelu obiektowego, uniemożliwiając ponowne poprzez dziedziczenie. Na przykład, klasa Wątek jest zamknięty. Chociaż widzę, że może chcieć nici być jak najbardziej skuteczny, mogę również wyobrazić scenariusze, gdzie będących w stanie podklasy wątek miałby wielkie korzyści. Autorzy klasy, jeśli trzeba uszczelnić swoje klasy do „Performance” powodów, proszę podać interfejs przynajmniej tak nie mamy zawinąć i zamień wszędzie, że potrzebujemy funkcji, zapomniałem.

Przykład: SafeThread miał owinąć klasy wątku ponieważ gwintu jest szczelnie zamknięty i nie ma interfejsu IThread; SafeThread automatycznie wychwytuje nieobsłużonych wyjątków na wątków, coś zupełnie brakuje klasy wątku. [a nie, nieobsłużone zdarzenia wyjątków czy nie odebrać nieobsłużonych wyjątków w wtórnych wątków].

</ Off-topic-rant>

Odpowiedział 14/10/2008 o 21:04
źródło użytkownik

głosy
3

Sealed zajęcia powinny zapewnić poprawę wydajności. Ponieważ uszczelniony klasy nie może być uzyskane, wirtualnych, użytkownicy mogą stać się nie wirtualnych członków.

Oczywiście, mówimy bardzo małe zyski. Nie chciałbym zaznaczyć klasę jako uszczelnione tak aby uzyskać wzrost wydajności o ile profilowanie wykazały, że jest to problem.

Odpowiedział 05/08/2008 o 13:37
źródło użytkownik

głosy
2

zajęcia zamknięte będą przynajmniej odrobinę szybciej, ale czasami może być waayyy szybciej ... jeśli Optimizer JIT może inline połączeń, które zostały inaczej połączeń wirtualnych. Więc, gdzie jest często nazywany metod, które są wystarczająco małe, aby być wstawiane, zdecydowanie rozważyć uszczelnianie klasę.

Jednak najlepszym powodem do uszczelniania klasę to znaczy „nie zaprojektować to być odziedziczone, więc nie zamierzam pozwolić poparzenia przy założeniu, że został zaprojektowany tak, a ja nie zamierzam palić się przez coraz zablokowana w implementacji, ponieważ pozwalają czerpać z niego.”

Wiem, że niektórzy tutaj mówią, że nienawidzą klas zapieczętowanych bo chcą możliwość czerpania z niczego ... ale to nie jest często najbardziej utrzymaniu wybór ... bo wystawiając klasę Wyprowadzenie zamki Ci dużo więcej niż nie narażając wszystko że. Jego podobna do powiedzenie „brzydzę klas, które mają członków prywatnych ... I często nie może sprawić, że klasa robić to, co chcę, bo nie mam dostępu.” Hermetyzacja jest ważne ... uszczelniający jest jedną z form hermetyzacji.

Odpowiedział 01/10/2011 o 11:37
źródło użytkownik

głosy
2

@Vaibhav, jaką pan wykonać testy do mierzenia wydajności?

Chyba trzeba by użyć Rotor i wiercić w CLI i zrozumieć, w jaki sposób uszczelniony klasa poprawi wydajność.

SSCLI (wirnik)
SSCLI: Rotor Programming

Common Language Infrastructure (CLI) jest standardem ECMA, który opisuje rdzeń Framework. Shared Źródło CLI (SSCLI), znany również jako Rotor, jest skompresowanym archiwum kodu źródłowego do realizacji roboczej ECMA CLI i specyfikacji ECMA języka C #, technologie w sercu architekturze .NET Microsoftu.

Odpowiedział 05/08/2008 o 13:40
źródło użytkownik

głosy
-9

Uruchomić ten kod, a zobaczysz, że zamknięte zajęcia są 2 razy szybciej:

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";
    }
}

Wyjście: Sealed klasa: 00: 00: +00,1897568 NonSealed klasa: 00: 00: +00,3826678

Odpowiedział 26/11/2009 o 18:50
źródło użytkownik

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more