Dělat Uzavřené třídy skutečně nabízejí výkonnostní výhody?

hlasů
118

Jsem narazil na spoustu tipů pro optimalizaci, které říkají, že byste měli označit své třídy, jak je uzavřen dostat další výhody výkonu.

Běžel jsem nějaké zkoušky, aby ověřil diferenciál výkon a nenašel. Dělám něco špatně? Jsem chybí případ, kdy uzavřené třídy bude dávat lepší výsledky?

Má někdo spustit testy a vidět nějaký rozdíl?

Pomoz mi učit :)

Položena 05/08/2008 v 13:00
zdroj uživatelem
V jiných jazycích...                            


11 odpovědí

hlasů
133

Odpověď je ne, uzavřených tříd nemají lepší výkon než non-uzavřen.

Tato otázka přijde na callvs callvirtoperačních kódů IL. Callje rychlejší než callvirta callvirtse používá hlavně když nevím, jestli je objekt byl podtřídy. Takže lidé předpokládají, že pokud uzavřete třídu všechny op kódy se změní z calvirtsna callsa bude rychlejší.

Bohužel callvirtdělá jiné věci, které dělají to užitečný i jako kontrola nulové reference. To znamená, že i v případě, že třída je uzavřen, referenční mohla zůstat null a tudíž callvirtje potřeba. Můžete obejít toto (bez nutnosti uzavřít třídu), ale to se stává tak trochu zbytečné.

Struktury používat call, protože nemohou být podtřídou a nikdy nejsou null.

Viz tuto otázku pro více informací:

Hovor a callvirt

Odpovězeno 24/12/2008 v 03:09
zdroj uživatelem

hlasů
46

Jitter bude někdy používají non-virtuální volání metod v uzavřených tříd, protože neexistuje žádný způsob, jakým lze dále rozšířit.

Existuje komplexní pravidla týkající se typu volání, virtuální / nonvirtual, a já nevím, je to všechno tak nemůžu nastínit je pro vás, ale pokud si google uzavřených tříd a virtuálních metod, které by mohly najít nějaké články na toto téma.

Všimněte si, že jakýkoliv druh výkonu ve prospěch byste získat z této úrovně optimalizace by měly být považovány za poslední instance, vždy optimalizovat na algoritmické úrovni, než optimalizovat na kódové úrovni.

Zde je jeden odkaz zmiňuji: Toulky na uzavřeném klíčové slovo

Odpovězeno 05/08/2008 v 13:32
zdroj uživatelem

hlasů
20

Pokud vím, neexistuje žádná záruka plnění ve prospěch. Ale je tu šance snížit snížení výkonu pod nějakou specifickou podmínku s uzavřené metody. (utěsněny třída je všechny metody, které mají být utěsněny.)

Ale je to na implementaci překladače a prováděcím prostředí.


podrobnosti

Mnoho moderních procesorů používat dlouhý plynovod struktury ke zvýšení výkonu. Vzhledem k tomu, CPU je neuvěřitelně rychleji než paměť, procesor má k předběžného načtení kódu z paměti k urychlení potrubí. V případě, že kód není připraven v řádném termínu, bude potrubí v nečinnosti.

K dispozici je velká překážka nazvaný dynamický výběr , který narušuje tento ‚prefetching‘ optimalizace. Můžete to pochopit, jak jen podmíněné větvení.

// 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 nemůže prefetch další kód k provedení v tomto případě proto, že další kód pozice není známa, dokud není vyřešen stav. Tak to dělá nebezpečí způsobí potrubí volnoběhu. A trest vystoupení volnoběhu je obrovský v pravidelné.

Podobná věc se stane v případě, že metoda najetí vozidel. Kompilátor může určit správnou metodu převažujícího pro aktuální volání metody, ale někdy je to nemožné. V tomto případě je vhodné metody lze určit pouze v době běhu. To je také případ dynamický výběr, a hlavním důvodem dynamicky zadali jazyky jsou obecně pomalejší než staticky zadali jazycích.

Některé CPU (včetně x86 čipy nedávné Intelu) používá techniku zvanou spekulativní provádění využít ropovod i na situaci. Jen pro načítání v předstihu jednoho z provedení dráhy. Ale hit sazba této techniky není tak vysoká. A selhání spekulace způsobuje potrubní stánek, který také dělá velké snížení výkonu. (to je zcela realizací CPU. některá mobilní CPU je známý jako není tento druh optimalizace pro úsporu energie)

Zjednodušeně řečeno, C # je staticky zkompilovaný jazyk. Ale ne vždy. Nevím přesně stav, a to je zcela na implementaci překladače. Některé kompilátory může eliminovat možnost dynamické odeslání tím, že brání metodu převažujícího je-li tento způsob označen jako sealed. Hloupý kompilátory nemusí. To je výkon ku prospěchu sealed.


Tato odpověď ( Proč je rychlejší zpracování seřazené pole, než s netříděným pole? ) Popisuje predikci větvení mnohem lépe.

Odpovězeno 03/02/2011 v 16:22
zdroj uživatelem

hlasů
18

Aktualizujte: Jak .NET jádra 2.0 a .NET Desktop 4.7.1 je CLR nyní podporuje devirtualization. To může trvat metody v uzavřených tříd a nahradit virtuální hovory s přímou volbou - a to lze provést také pro non-uzavřeny tříd, pokud může přijít na to, že je to bezpečné.

V takovém případě (uzavřená třída, která CLR nemohl jinak detekovat jako bezpečná devirtualise), uzavřená třída by měla ve skutečnosti nabídnout určitý výkonnostní výhody.

To znamená, že by mě nenapadlo, že by to stálo za to dělat starosti , pokud jste již profilované kód a zjistil, že jsi byl v obzvlášť horké dráze volána miliónkrát, nebo něco takového:

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


Původní odpověď:

Udělal jsem následující testovací program, a pak rozložit jej pomocí Reflektor vidět, co MSIL kód byl signál.

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

Ve všech případech je C # kompilátor (Visual Studio 2010 v konfiguraci Release build) vyzařuje identický MSIL, který je takto:

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-citoval z důvodů, proč lidé říkají, že uzavře poskytuje výhody výkonu je, že kompilátor ví, že třída není přepsat, a tak můžete použít callmísto callvirt, protože nemusí kontrolovat virtuálů apod Jak prokázáno výše, to není skutečný.

Moje další myšlenka byla, že i když je MSIL je totožný, snad JIT překladač zachází uzavřeny tříd jinak?

Běžel jsem na sestavení uvolnění pod ladicí program Visual Studio a prohlížel decompiled x86 výstupu. V obou případech je x86 kód byl identický, s výjimkou jmen tříd a funkce paměťových adres (které samozřejmě musí být odlišné). Tady to je

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

Pak jsem si myslel snad spuštěna pod ladicí způsobí to provádět méně agresivní optimalizace?

Pak jsem běžel samostatná zpráva stavět spustitelný mimo jakékoliv ladění prostředí, a používá windbg + SOS zlomit v poté, co program dokončil a zobrazit dissasembly o JIT zkompilovaný x86 kód.

Jak můžete vidět z níže uvedený kód, při jízdě mimo ladicí JIT překladač je více agresivní, a to inline na WriteItmetodu přímo do volajícího. Zásadní věcí však je, že to byla stejná při volání utěsněný vs non-zapečetěný třídě. Není žádný rozdíl mezi vůbec utěsněné či nonsealed třídě.

Tady to je, když volá normální třída:

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 zapouzdřené třídy:

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

Pro mě to poskytuje solidní důkaz, že tam nemůže být jakýkoliv zlepšení výkonu mezi volání metod na uzavřených vs tříd neuzavřených ... Myslím, že jsem teď radost :-)

Odpovězeno 20/02/2012 v 21:45
zdroj uživatelem

hlasů
4

Značení třída sealedby neměla mít žádný vliv na výkon.

Existují případy, kdy cscby mohly mít, aby vydávaly callvirtopcode namísto callopcode. Nicméně se zdá, tyto případy jsou vzácné.

A zdá se mi, že JIT by měla být schopna vydávat stejné volání funkce non-virtuální pro callvirtkteré by to pro call, dozví-li se, že třída nemá žádné podtřídy (zatím). Kdyby jen jeden implementace metody existuje, neexistuje žádný bod nahrává svou adresu z vtable, stačí zavolat na jedno provedení přímo. Když na to přijde, JIT může dokonce inline funkci.

Je to tak trochu sázka do loterie ze strany SVT je, protože v případě, že podtřídy se později načten, JIT bude muset vyhodit, že strojový kód a znovu sestavit kód, který vyzařuje skutečnou virtuální volání. Můj odhad je, to se nestává často v praxi.

(A ano, VM designéři opravdu agresivně prosazovat tyto drobné zlepšení v poměru.)

Odpovězeno 20/11/2009 v 10:53
zdroj uživatelem

hlasů
3

Považuji „uzavřen“ tříd normální věc a vždycky mít důvod vynechat „uzavřena“ klíčové slovo.

Nejdůležitější důvody pro mě jsou:

a) Lepší kompilaci kontroly času (odlévání na rozhraní neuplatnily budou detekovány v době kompilace, a to nejen při běhu)

a hlavní důvod:

b) Zneužití mých tříd není možné, aby způsob,

Přál bych si, Microsoft by mohl odehrát „uzavřen“ standard, ne „neutěsněný“.

Odpovězeno 03/08/2010 v 15:27
zdroj uživatelem

hlasů
3

<Off-topic-chvástat>

I hnusí uzavřených tříd. I v případě, že výhody výkonnosti jsou ohromující (o čemž pochybuji), že zničí objektově orientovaný model, tím, že brání opětovné použití prostřednictvím dědičnosti. Například třída Thread je uzavřen. I když vidím, že jeden by mohl chtít závity být co nejefektivnější, mohu také představit scénáře, kdy ho nemohly podtřídy vlákno bude mít velký přínos. Autoři třídy, když se musí utěsnit své třídy pro „Výkon“ důvodů, prosím, poskytnout rozhraní, přinejmenším proto, že nemáme zabalit a nahrazení všude tam, kde potřebujeme funkci, kterou zapomněl.

Příklad: SafeThread musel zabalit třídu závitu, protože závit je uzavřen a není IThread rozhraní; SafeThread automaticky zachycuje neošetřené výjimky na závity, něco úplně chybí ze třídy Thread. [a ne, že neošetřené výjimce události se ani zvednout neošetřené výjimky na středních závity].

</ Off-topic-chvástat>

Odpovězeno 14/10/2008 v 21:04
zdroj uživatelem

hlasů
3

Uzavřené třídy by měly zajistit zlepšení výkonu. Vzhledem k tomu, uzavřená třída nelze odvodit jakékoliv virtuální členové mohou být obrácená do non-virtuální členů.

Samozřejmě, mluvíme opravdu malé zisky. Já bych označit třídu jako zapečetěný jen proto, aby zlepšení výkonu, pokud profilování je odhalen být problém.

Odpovězeno 05/08/2008 v 13:37
zdroj uživatelem

hlasů
2

Uzavřené třídy bude alespoň trošku rychlejší, ale někdy může být waayyy rychlejší ... v případě, že pro optimalizaci JIT může vložené hovory, které by jinak byly virtuální volání. Takže tam, kde je často zvané metody, které jsou dostatečně malé, aby se inline rozhodně zvážit uzavření třídu.

Nicméně, nejlepší důvod k utěsnění třídu znamená „jsem nenavrhl, aby to bylo zděděné od, takže nebudu, aby se spálíte za předpokladu, že byl navržen tak, aby to a nehodlám hořet sám tím, že se zamkl do implementace, protože jsem tě nechal odvodit z ní.“

Vím, že někteří zde řekli, že nenávidí uzavřených tříd, protože chtějí možnost vyplývá z ničeho ... ale to často není nejvíce snadno udržovatelné výběr ... protože vystavení třídu k odvození zámky vás mnohem víc, než by vystavil all že. Jeho podobné říká: „Nenávidím tříd, které mají vlastní členy ... I často nemohou dělat třída dělat, co chci, protože nemám přístup.“ Zapouzdření je důležité ... těsnění je jedna z forem zapouzdření.

Odpovězeno 01/10/2011 v 11:37
zdroj uživatelem

hlasů
2

@Vaibhav, jaký druh zkoušky jste provést měření výkonu?

Myslím, že by člověk musel použít Rotor a vrtat do CLI a pochopit, jak by utěsněná třída zlepšit výkon.

SSCLI (Rotor)
SSCLI: Shared Source Common Language Infrastructure

Common Language Infrastructure (CLI) je standardní ECMA, který popisuje jádro .NET Framework. Sdílené Source CLI (SSCLI), také známý jako Rotor, je komprimovaný archiv zdrojového kódu pracovní provádění ECMA CLI a specifikace # jazyka ECMA C, technologie v srdci .NET architektuře Microsoftu.

Odpovězeno 05/08/2008 v 13:40
zdroj uživatelem

hlasů
-9

Tento kód spustit, a uvidíte, že uzavřené třídy jsou 2 krát rychlejší:

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

Výstup: Sealed třída: 00: 00: 00,1897568 NonSealed třída: 00: 00: 00,3826678

Odpovězeno 26/11/2009 v 18:50
zdroj uživatelem

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