Gjør lukkede klasser virkelig tilbyr ytelsesfordeler?

stemmer
121

Jeg har kommet over en rekke optimalisering tips som sier at du bør merke klassene som forseglet for å få ekstra ytelsesfordeler.

Jeg kjørte noen tester for å sjekke ytelsen differensial og fant ingen. Gjør jeg noe galt? Jeg mangler tilfelle hvor forseglet klasser vil gi bedre resultater?

Har noen kjøre tester og sett en forskjell?

Hjelp meg å lære :)

Publisert på 05/08/2008 klokken 12:00
kilden bruker
På andre språk...                            


11 svar

stemmer
134

Svaret er nei, forseglet klassene ikke presterer bedre enn ikke-forseglet.

Spørsmålet kommer ned til callvs callvirtIL op koder. Caller raskere enn callvirt, og callvirter i hovedsak brukt når du ikke vet om objektet er subclassed. Så folk antar at hvis du forsegle en klasse alle op koder vil endre seg fra calvirtså callsog vil være raskere.

Dessverre callvirtgjør andre ting som gjør det nyttig også, som å sjekke for null referanser. Dette betyr at selv om en klasse er forseglet, kan referansen fortsatt være null og dermed callvirter nødvendig. Du kan komme rundt dette (uten å forsegle klasse), men det blir litt meningsløst.

Structs bruke callfordi de ikke kan subclassed og er aldri null.

Se på dette spørsmålet for mer informasjon:

Call og callvirt

Svarte 24/12/2008 kl. 02:09
kilden bruker

stemmer
50

Jitter vil noen ganger bruke ikke-virtuelle samtaler til metoder i lukkede klasser siden det er ingen måte de kan utvides ytterligere.

Det er komplekse regler om telefontype, virtuell / nonvirtual, og jeg vet ikke dem alle så jeg kan egentlig ikke skissere dem for deg, men hvis du google for lukkede klasser og virtuelle metoder du kan finne noen artikler om emnet.

Merk at enhver form for ytelse fordelen du vil få fra dette nivået av optimalisering bør betraktes som siste utvei, alltid optimalisere på algoritmisk nivå før du optimalisere på kodenivå.

Her er en lenke nevne dette: Rambling på forseglet søkeord

Svarte 05/08/2008 kl. 12:32
kilden bruker

stemmer
21

Som jeg vet, er det ingen garanti for ytelse fordel. Men det er en sjanse til å redusere ytelsen straffes etter noen spesifikke tilstand med lukket metode. (forseglet klasse gjør alle metoder som skal tettes.)

Men det er opp til kompilatoren implementering og gjennomføring miljø.


detaljer

Mange av moderne prosessorer bruker lang rørledning struktur for å øke ytelsen. Fordi CPU er utrolig raskere enn minne, har CPU til prefetch kode fra minnet til å akselerere rørledningen. Hvis koden er ikke klar på riktig tid, vil rørledningene være inaktiv.

Det er et stort hinder kalt dynamisk tildeling som forstyrrer denne 'forhåndshenting' optimalisering. Du kan forstå dette som bare en betinget forgrening.

// 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 kan ikke prefetch neste koden som skal utføres i dette tilfellet fordi neste kode posisjon er ukjent inntil tilstanden er løst. Så dette gjør fare fører rørledning inaktiv. Og ytelse straffe av inaktiv er stort i regelmessig.

Lignende ting skjer i tilfelle av metode altoverskyggende. Kompilatoren kan avgjøre riktig metode altoverskyggende for dagens metode samtale, men noen ganger er det umulig. I dette tilfellet, kan passende metode bestemmes bare under kjøring. Dette er også et tilfelle av dynamisk forsendelse, og hovedgrunnen for dynamisk skrevet språk er generelt tregere enn statisk skrevet språk.

Noen CPU (inkludert nylig Intels x86 chips) bruker teknikk som kalles spekulativ utførelse å benytte rørledningen selv om situasjonen. Bare prefetch en henrettelses bane. Men hit rate av denne teknikken er ikke så høy. Og spekulasjoner svikt forårsaker rørledning stall som også gjør stor ytelsen. (dette er helt etter CPU implementering. noen mobile CPU er kjent som ikke denne typen optimalisering for å spare energi)

I utgangspunktet er C # en statisk kompilert språk. Men ikke alltid. Jeg vet ikke eksakt tilstand, og dette er helt opp til kompilatoren gjennomføring. Noen kompilatorer kan eliminere muligheten for dynamisk tildeling ved å hindre metode styrende hvis metoden er merket som sealed. Dumme kompilatorer kanskje ikke. Dette er ytelsen fordel for sealed.


Dette svaret ( Hvorfor er det raskere å behandle en sortert utvalg enn en usortert array? ) Beskriver grenen prediksjon mye bedre.

Svarte 03/02/2011 kl. 15:22
kilden bruker

stemmer
19

Oppdatering: Fra .NET kjerne 2.0 og .NET Desktop 4.7.1, CLR støtter nå devirtualization. Det kan ta metoder i lukkede klasser og erstatte virtuelle samtaler med direkte samtaler - og det kan også gjøre dette for ikke-forseglet klasser dersom det kan finne ut at det er trygt å gjøre det.

I et slikt tilfelle (en forseglet klasse som CLR ellers ikke kunne oppdage så trygt å devirtualise), bør en forseglet klasse faktisk tilby noen form for ytelse fordel.

Når det er sagt, jeg ville ikke tro det ville være verdt å bekymre deg med mindre du hadde allerede profilert koden og funnet ut at du var i en spesielt varm stien som blir kalt millioner av ganger, eller noe sånt:

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


Original Svar:

Jeg gjorde følgende testprogram, og deretter decompiled den ved hjelp av reflektor for å se hva MSIL koden ble sluppet ut.

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

I alle tilfeller, C # kompilatoren (Visual Studio 2010 i versjon bygge konfigurasjon) avgir identisk MSIL, som er som følger:

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 

Den ofte siterte grunn til at folk sier forseglet gir ytelsesfordeler er at kompilatoren vet klassen er ikke overstyrt, og dermed kan bruke calli stedet for callvirtsom det ikke trenger å se etter Virtuals etc. Som vist ovenfor, er dette ikke ekte.

Min neste tanke var at selv om MSIL er identiske, kanskje JIT kompilatoren behandler forseglet klasser annerledes?

Jeg kjørte en utgivelse bygge under Visual Studio debugger og sett på decompiled x86 utgang. I begge tilfeller, x86-kode var identiske, med unntak av klasse navn og funksjon minneadresser (som selvfølgelig må være forskjellig). Her er det

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

Jeg så tenkte kanskje kjører under debugger fører det til å utføre mindre aggressiv optimalisering?

Jeg så løp en frittstående utgivelse bygge kjør utenom eventuelle debugging miljøer, og brukes WinDbg + SOS å bryte inn etter at programmet var ferdig, og vise dissasembly av JIT kompilert x86-kode.

Som du kan se fra koden nedenfor, når du kjører utenfor debugger JIT-kompilator er mer aggressive, og det har inlined den WriteItmetoden rett inn i ringer. Det viktigste er imidlertid at det var identisk når du ringer en forseglet vs ikke-forseglet klasse. Det er ingen forskjell overhodet mellom en forseglet eller nonsealed klasse.

Her er det når du ringer en normal 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 en forseglet 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

For meg gir dette solid bevis på at det ikke kan være noen ytelsesforbedring mellom å kalle metoder på forseglet vs ikke-forseglet klasser ... Jeg tror jeg er glad nå :-)

Svarte 20/02/2012 kl. 20:45
kilden bruker

stemmer
4

Merking en klasse sealedbør ha noe ytelsen påvirkes.

Det finnes tilfeller der cscmåtte ha til å avgi en callvirtopcode i stedet for en callopcode. Imidlertid synes det disse tilfellene er sjeldne.

Og det synes for meg at JIT skal kunne avgi den samme ikke-virtuelle funksjonskall for callvirtat det ville for call, hvis den vet at klassen ikke har noen underklasser (ennå). Hvis bare en implementering av metoden finnes, er det ingen vits å laste sin adresse fra en vtable-bare ringe en implementering direkte. For den saks skyld, kan JIT selv inline funksjonen.

Det er litt av en gamble på JIT del, fordi hvis en underklasse er senere lastet, vil JIT nødt til å kaste bort den maskinen kode og kompilere koden på nytt, sender ut en ekte virtuell samtale. Min gjetning er at dette ikke skjer ofte i praksis.

(Og ja, VM designere virkelig aggressivt forfølge disse små ytelse vinner.)

Svarte 20/11/2009 kl. 09:53
kilden bruker

stemmer
3

Jeg vurderer "forseglet" klasser normal saken og jeg har alltid en grunn til å utelate "forseglet" søkeord.

De viktigste årsakene til meg er:

a) bedre sammenstille tidskontroller (støping til grensesnitt ikke gjennomført, vil bli detektert ved kompilering, ikke bare under kjøring)

og beste grunnen:

b) Misbruk av mine klasser er ikke mulig på den måten

Jeg ønsker Microsoft ville ha gjort "forseglet" standard, ikke "utette".

Svarte 03/08/2010 kl. 14:27
kilden bruker

stemmer
3

<Off-topic-rant>

Jeg hater lukkede klasser. Selv om den gode ytelse er forbløffende (som jeg tvil), de ødelegger objektorientert modell ved å forhindre gjenbruk via arv. For eksempel er tråden klassen forseglet. Mens jeg kan se at en vil kanskje tråder å være så effektiv som mulig, kan jeg også tenke meg scenarioer der å kunne underklasse tråden ville ha store fordeler. Klasse forfattere, hvis du forsegle klassene for "performance" grunner, må du gi et grensesnitt i det minste slik at vi ikke trenger å pakke inn-og-erstatt overalt at vi trenger en funksjon du har glemt.

Eksempel: SafeThread måtte vikle tråden klassen fordi tråden er forseglet og det er ingen IThread grensesnitt; SafeThread feller automatisk Ubehandlede unntak på tråder, noe helt mangler fra tråden klassen. [og ingen, de ubehandlet unntak arrangementer do ikke plukke opp Ubehandlede unntak i sekundærtråder].

</ Off-topic-rant>

Svarte 14/10/2008 kl. 20:04
kilden bruker

stemmer
3

Forseglede klasser bør gi en ytelsesforbedring. Eftersom et lukket klasse ikke kan være avledet, kan eventuelle virtuelle medlemmer vendes til ikke-virtuelle medlemmer.

Selvfølgelig, vi snakker virkelig små gevinster. Jeg ville ikke merke en klasse som forseglet bare for å få en bedre ytelse med mindre profilering avslørt at det skal være et problem.

Svarte 05/08/2008 kl. 12:37
kilden bruker

stemmer
2

forseglede klasser vil være minst en liten bit raskere, men noen ganger kan være waayyy raskere ... hvis JIT Optimizer kan inline samtaler som ellers ville ha blitt virtuelle samtaler. Så, hvor det er ofte kalt metoder som er små nok til å bli inlined, definitivt vurdere forsegling klassen.

Men den beste grunnen til å forsegle en klasse er å si "jeg ikke design dette å være arvet fra, så jeg kommer ikke til å la deg få brent ved å anta det var designet for å være slik, og jeg kommer ikke til å brenne meg selv ved å bli låst inn i en implementerings fordi jeg la deg avlede fra det."

Jeg vet at noen her har sagt at de hater lukkede klasser fordi de ønsker muligheten til å komme fra noe ... men det er ofte ikke det mest vedlikeholds valg ... fordi utsette en klasse for avledning låser deg i en mye mer enn ikke å utsette alle at. Dens ligner sier "Jeg hater klasser som har private medlemmer ... Jeg ofte ikke kan gjøre klassen gjøre hva jeg vil, fordi jeg ikke har tilgang." Innkapsling er viktig ... tetning er en form for innkapsling.

Svarte 01/10/2011 kl. 10:37
kilden bruker

stemmer
2

@Vaibhav, hva slags tester har du utfører for å måle ytelsen?

Jeg antar at man må bruke Rotor og å bore i CLI og forstå hvordan en forseglet klassen ville forbedre ytelsen.

SSCLI (Rotor)
SSCLI: Shared Source Common Language Infrastructure

Common Language Infrastructure (CLI) er ECMA standard som beskriver kjernen av .NET Framework. Shared Source CLI (SSCLI), også kjent som Rotor, er en komprimert arkiv av kildekoden til en fungerende implementering av ECMA CLI og ECMA C # språkspesifikasjonen, teknologier i hjertet av Microsofts .NET arkitektur.

Svarte 05/08/2008 kl. 12:40
kilden bruker

stemmer
-9

Kjør denne koden, og du vil se at lukkede klasser er 2 ganger raskere:

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

Utgangen: Forseglet klasse: 00: 00: 00,1897568 NonSealed klasse: 00: 00: 00,3826678

Svarte 26/11/2009 kl. 17:50
kilden bruker

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