Fazer aulas selados realmente oferecem benefícios de desempenho?

votos
118

Eu vim através de um monte de dicas de otimização que dizer que você deve marcar suas aulas como selados para obter benefícios de desempenho extra.

Corri alguns testes para verificar o diferencial de desempenho e não o achou. Estou fazendo algo errado? Estou faltando o caso em que as classes selados dará melhores resultados?

Tem testes alguém correr e viu a diferença?

Me ajudar a aprender :)

Publicado 05/08/2008 em 13:00
fonte usuário
Em outras línguas...                            


11 respostas

votos
133

A resposta é não, as classes selados não executam melhor do que não-selado.

A questão se resume ao callvs callvirtcódigos op IL. Callé mais rápido do que callvirt, e callvirté usado principalmente quando você não sabe se o objeto foi subclasse. Então, as pessoas assumem que se você selar uma classe todos os códigos op mudará de calvirtspara callse será mais rápido.

Infelizmente callvirtfaz outras coisas que o tornam útil também, como a verificação de referências nulas. Isto significa que mesmo se uma classe é selado, a referência ainda pode ser nulo e, portanto, um callvirté necessário. Você pode contornar isso (sem a necessidade de selar a classe), mas torna-se um pouco inútil.

Structs usar callporque eles não podem ser uma subclasse e nunca são nulos.

Veja esta questão para mais informações:

Chamada e callvirt

Respondeu 24/12/2008 em 03:09
fonte usuário

votos
46

O jitter, às vezes, usar chamadas não-virtuais para métodos nas classes selados já que não há maneira que pode ser estendido ainda mais.

Existem regras complexas quanto ao tipo de chamada, virtual / não virtuais, e eu não sei todos eles, então eu realmente não posso descrever-los para você, mas se você google para as classes seladas e métodos virtuais que você pode encontrar alguns artigos sobre o tema.

Note-se que qualquer tipo de benefício de desempenho que você obteria a partir deste nível de otimização deve ser considerado como último recurso, sempre otimizar no nível algorítmico antes de otimizar no nível do código.

Aqui está um link de mencionar isto: divagar sobre a palavra-chave selada

Respondeu 05/08/2008 em 13:32
fonte usuário

votos
20

Como eu sei, não há nenhuma garantia de benefício de desempenho. Mas há uma chance de diminuir a penalidade de desempenho sob alguma condição específica com o método selado. (classe selado torna todos os métodos a serem selados.)

Mas cabe a implementação do compilador e ambiente de execução.


detalhes

Muitos dos CPUs modernas usar a estrutura gasoduto longa para aumentar o desempenho. Como a CPU é incrivelmente mais rápido do que a memória, CPU tem de pré-busca de código a partir da memória para acelerar pipeline. Se o código não estiver pronto no momento adequado, os pipelines será ocioso.

Há um grande obstáculo chamado expedição dinâmica que perturba essa otimização 'pré-busca'. Você pode entender isso como apenas uma ramificação condicional.

// 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 não pode prefetch seguinte código para executar neste caso, porque a posição de código seguinte é desconhecido até que a condição seja resolvida. Então, isso faz com perigo provoca ocioso pipeline. E penalidade de desempenho por ocioso é enorme em regular.

Algo semelhante acontece no caso de primordial método. Compilador pode determinar primordial método adequado para a chamada de método atual, mas às vezes é impossível. Neste caso, o método adequado pode ser determinado apenas em tempo de execução. Este é também um caso de expedição dinâmica, e, a principal razão de línguas dinamicamente digitados são geralmente mais lentos do que as linguagens estaticamente tipadas.

Alguns CPU (incluindo chips x86 recente da Intel) usa técnica chamada execução especulativa utilizar gasoduto até mesmo sobre a situação. Apenas pré-busca de um dos caminho de execução. Mas a taxa de acerto de esta técnica não é tão alta. E insuficiência especulação provoca tenda gasoduto que também faz grande penalidade de desempenho. (isso é completamente pela implementação CPU. alguma CPU móvel é conhecida como não faz esse tipo de otimização para economizar energia)

Basicamente, C # é uma linguagem estaticamente compilado. Mas não sempre. Eu não sei condição exata e isso é inteiramente até implementação do compilador. Alguns compiladores pode eliminar possibilidade de envio dinâmica, impedindo método de substituição se o método está marcado como sealed. Compiladores estúpidos não pode. Este é o benefício do desempenho sealed.


Essa resposta ( Por que é mais rápido para processar uma matriz classificada de uma matriz não ordenada? ) Está descrevendo a previsão de desvios muito melhor.

Respondeu 03/02/2011 em 16:22
fonte usuário

votos
18

UPDATE: A partir do .NET Núcleo 2.0 e .NET ambiente de trabalho 4.7.1, o CLR suporta agora devirtualization. Pode levar em classes selados e substituir chamadas virtuais com chamadas diretas - e também pode fazer isso para as classes não selada se ele pode descobrir que é seguro fazê-lo.

Num caso (a classe sealed que o CLR não poderiam detectar como seguro devirtualise), uma classe selada deve realmente oferecer algum tipo de benefício de desempenho.

Dito isto, eu não acho que seria vale a pena se preocupar com a não ser que você já tinha perfilado o código e determinou que você estava em um caminho particularmente quente que está sendo chamado de milhões de vezes, ou algo parecido:

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


Resposta Original:

Eu fiz o seguinte programa de teste, e depois compilado-lo com refletor para ver o código MSIL foi emitida.

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

Em todos os casos, o compilador C # (Visual Studio 2010 na configuração de compilação Release) emite idêntica MSIL, que é a seguinte:

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 

A razão citada frequentemente que as pessoas dizem selada proporciona benefícios de desempenho é que o compilador sabe que a classe não é anulado, e, portanto, pode usar callem vez de callvirtuma vez que não tem que verificar para virtuals, etc. Como provado acima, esta não é verdade.

Meu próximo pensamento foi que, embora o MSIL é idêntico, talvez o compilador JIT trata selado aulas de forma diferente?

Eu corri uma compilação de lançamento sob o depurador visual studio e visto a saída x86 decompiled. Em ambos os casos, o código x86 era idêntico, com exceção de nomes de classes e endereços de memória de função (que, naturalmente, deve ser diferente). Aqui está

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

Pensei então talvez executando sob o depurador faz com que ele para executar otimização menos agressivo?

Então eu corri uma versão standalone construir fora executável de quaisquer ambientes de depuração, e usado WinDBG + SOS para quebrar depois de o programa tinha terminado, e ver o dissasembly do código x86 JIT compilado.

Como você pode ver no código a seguir, quando executado fora do depurador compilador JIT é mais agressivo, e tem embutido o WriteItmétodo direto para o chamador. O mais importante, porém, é que ele era idêntico ao chamar um selado vs classe não-selado. Não há diferença alguma entre uma classe selada ou não selada.

Aqui está ele ao chamar uma classe normal:

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 uma classe selada:

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

Para mim, isso proporciona uma prova sólida que não pode ser qualquer melhoria de desempenho entre chamar métodos em selado vs classes não selada ... Eu acho que estou feliz agora :-)

Respondeu 20/02/2012 em 21:45
fonte usuário

votos
4

Marcação de uma classe sealednão deve ter nenhum impacto no desempenho.

Há casos em que cscpossa ter para emitir um callvirtcódigo de operação em vez de um callcódigo de operação. No entanto, parece esses casos são raros.

E parece-me que o JIT deve ser capaz de emitir a mesma chamada de função não-virtual para callvirtque ele faria para call, se ele sabe que a classe não tem nenhum subclasses (ainda). Se apenas uma implementação do método existe, não há nenhum ponto de carregar seu endereço a partir de uma vtable-apenas chamar a implementação diretamente. Para essa matéria, o JIT pode até inline função.

É um pouco de uma aposta por parte do JIT, porque se uma subclasse é depois carregado, o JIT terá que jogar fora que código de máquina e compilar o código novamente, emitindo uma chamada virtual real. Meu palpite é que isso não acontece muitas vezes na prática.

(E sim, os designers VM realmente perseguir agressivamente essas pequenas vitórias de desempenho.)

Respondeu 20/11/2009 em 10:53
fonte usuário

votos
3

Eu considero "selados" classes caso normal e eu sempre tenho uma razão para omitir a palavra-chave "selado".

As razões mais importantes para mim são:

a) Melhor compilar verificações de tempo (lançando a interfaces não implementadas serão detectados em tempo de compilação, não só em tempo de execução)

e, razão superior:

b) Abuso de minhas classes não é possível que maneira

Desejo Microsoft teria feito "selado" o padrão, não "não selada".

Respondeu 03/08/2010 em 15:27
fonte usuário

votos
3

<Off-topic-rant>

Eu abomino as classes selados. Mesmo se os benefícios de desempenho são surpreendentes (o que duvido), eles destroem o modelo orientado a objetos, impedindo a reutilização através de herança. Por exemplo, a classe Thread está selado. Enquanto eu posso ver que se pode querer tópicos para ser o mais eficiente possível, eu também posso imaginar cenários onde sendo capaz de subclasse Tópico teria grandes benefícios. Autores de classe, se você deve selar suas aulas por motivos de "desempenho", favor fornecer uma interface pelo menos assim não temos para embrulhar e substituir em todos os lugares que precisamos de um recurso que você esqueceu.

Exemplo: SafeThread teve para quebrar o classe Thread porque Tópico está fechado e não há nenhuma interface IThread; SafeThread armadilhas automaticamente exceções não tratadas em threads, algo completamente ausente da classe Thread. [e não, os eventos de exceção não tratada que não pegar exceções não tratadas em threads secundários].

</ Off-topic-rant>

Respondeu 14/10/2008 em 21:04
fonte usuário

votos
3

Classes seladas deve proporcionar uma melhoria de desempenho. Desde uma classe selada não pode ser derivada, todos os membros virtuais podem ser transformados em membros não-virtuais.

Claro, estamos falando realmente pequenos ganhos. Eu não iria marcar uma classe como selada apenas para obter uma melhoria de desempenho a menos de perfis revelou a ser um problema.

Respondeu 05/08/2008 em 13:37
fonte usuário

votos
2

Classes seladas será de pelo menos um pouquinho mais rápido, mas às vezes pode ser waayyy mais rápido ... se o Optimizer JIT pode in-line chamadas que teriam chamadas virtuais de outra forma sido. Então, onde há frequentemente chamados de métodos que são pequenos o suficiente para ser embutido, definitivamente considerar selar a classe.

No entanto, a melhor razão para selar uma classe é dizer "eu não projetar isso para ser herdada de, então eu não vou deixar você ficar queimado por supondo que ele foi projetado para ser assim, e eu não vou para me queimar por ficar trancado em uma implementação porque eu deixá-lo dela derivam ".

Eu sei que alguns aqui disseram que odeiam aulas fechados, porque eles querem a oportunidade de deriva de nada ... mas isso não é muitas vezes a escolha mais sustentável ... porque expondo uma classe para derivação fechaduras-lo em muito mais do que não expondo tudo aquele. Sua semelhante a dizer "eu detesto classes que têm membros privados ... Eu muitas vezes não pode fazer a classe fazer o que eu quero porque eu não tenho acesso." A encapsulação é importante ... vedação é uma forma de encapsulamento.

Respondeu 01/10/2011 em 11:37
fonte usuário

votos
2

@Vaibhav, que tipo de testes que você executar para medir o desempenho?

Eu acho que um teria que usar Rotor e para perfurar CLI e entender como uma classe selada iria melhorar o desempenho.

SSCLI (Rotor)
SSCLI: Infra-estrutura Shared Source Common Language

O Common Language Infrastructure (CLI) é o padrão ECMA que descreve o núcleo do .NET Framework. A fonte CLI Comum (SSCLI), também conhecido como Rotor, é um arquivo compactado do código-fonte de um trabalho de implementação da ECMA CLI ea especificação # linguagem ECMA C, as tecnologias que estão no centro da arquitetura .NET da Microsoft.

Respondeu 05/08/2008 em 13:40
fonte usuário

votos
-9

Executar este código e você verá que as classes selados são 2 vezes mais rápido:

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

saída: classe Sealed: 00: 00: class 00,1897568 não selada: 00: 00: 00,3826678

Respondeu 26/11/2009 em 18:50
fonte usuário

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