Pergunta

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

Eu executei alguns testes para verificar o desempenho diferencial e não encontrou nada.Estou fazendo algo errado?Estou perdendo o caso onde selado classes dará melhores resultados?

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

Ajuda-me a aprender :)

Foi útil?

Solução

O JITter usam, às vezes, não virtual chamadas para métodos selado classes, uma vez que não há nenhuma maneira que pode ser estendido ainda mais.

Existem regras complexas a respeito de chamada tipo, virtual/nonvirtual, e eu não sei que todos eles, então eu realmente não posso esboçar-los para você, mas se você procurar no google por selado classes e métodos virtuais você pode encontrar alguns artigos sobre o tema.

Note que qualquer tipo de benefício em termos de desempenho que você gostaria de obter a partir deste nível de otimização deve ser considerado como último recurso, sempre otimizar os algoritmos de nível antes de otimizar o nível de código.

Aqui está um link mencionando isso: Caminhadas na selado palavra-chave

Outras dicas

A resposta é não, selado classes não um desempenho melhor do que os não-seladas.

O problema se resume ao call vs callvirt IL códigos op. Call é mais rápido do que callvirt, e callvirt é usado principalmente quando você não sabe se o objeto de uma subclasse.Então, as pessoas assumem que, se você selo de uma classe de todos os códigos op vai mudar a partir de calvirts para calls e vai ser mais rápido.

Infelizmente callvirt faz outras coisas que o tornam útil também, como a verificação de nulo referências.Isso significa que, mesmo se uma classe é vedado, a referência poderá ainda ser nulo e, portanto, uma callvirt é necessário.Você pode contornar esse (sem a necessidade de selo classe), mas se torna um pouco inútil.

Estruturas de uso call porque eles não podem ser uma subclasse e nunca são nulos.

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

Chamada e callvirt

Atualização:Como de .NET Core 2.0 e .NET ambiente de Trabalho 4.7.1, o CLR agora suporta devirtualization.Pode demorar métodos selado classes e substituir chamadas virtuais com chamadas diretas - e ele também pode fazer isso para não-selado classes se pode descobrir é seguro fazê-lo.

Em tal caso, (uma classe selada que o CLR não poderiam detectar como seguro devirtualise), uma classe selada, na verdade, deve oferecer algum tipo de benefício.

O que disse, eu não acho que seria a pena se preocupar com a menos que você já tinha perfilado o código e determinou que você estava particularmente quente caminho que está sendo chamado de milhões de vezes, ou algo como isso:

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


Original Resposta:

Eu fiz o seguinte teste do programa e, em seguida, descompilado-lo usando Refletor para ver o que 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 Release configuração de compilação) emite idênticos MSIL, que é o 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 

Frequentemente citado razão que as pessoas dizem selado fornece benefícios de desempenho é que o compilador sabe que a classe não é anulado, e, portanto, podem usar call em vez de callvirt como ele não tem para verificar virtuals, etc.Como comprovado acima, isso não é verdade.

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

Eu corri uma versão de compilação sob o depurador do visual studio e visto o descompilado x86 de saída.Em ambos os casos, o código x 86 foi idêntico, com exceção de nomes de classe e função de endereços de memória (que, naturalmente, tem de ser diferente).Aqui é

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

Eu, então, pensei que, talvez, em execução sob o depurador faz com que ele execute menos agressivo otimização?

Eu, então, correu um autônomo compilação de versão executável fora de quaisquer ambientes de depuração, e utilizado o WinDBG + SOS para quebrar após o programa tinha concluído, e ver o dissasembly do JIT compilado código x 86.

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

Aqui é quando a chamada de 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 sólida prova de que não ser qualquer melhoria de desempenho entre chamar métodos no selado vs não-selado aulas...Eu acho que eu estou feliz agora :-)

Que eu saiba, não há nenhuma garantia de benefício de desempenho.Mas não há uma chance de diminuir a penalidade de desempenho sob alguma condição específica com selada método.(classe selada faz todos os métodos para ser selado.)

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


Detalhes

Muitas das CPUs modernas uso a longo pipeline estrutura para aumentar o desempenho.Porque CPU é incrivelmente mais rápido do que a memória, a CPU tem a pré-busca de código da memória para acelerar o pipeline.Se o código não está pronto no momento adequado, as tubulações de ser ociosos.

Há um grande obstáculo chamado distribuição dinâmica o que interrompe a este "prefetching' otimização.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 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();

CPU não pode prefetch seguinte código para executar a este caso, pois a próxima posição de código é desconhecido até que o problema seja resolvido.Então isso faz de perigo faz com pipeline ocioso.E o desempenho pena de inactividade é enorme regular.

Coisa semelhante acontece no caso do método primordial.Compilador pode determinar o método adequado primordial para a corrente chamada de método, mas às vezes é impossível.Neste caso, o método adequado pode ser determinada apenas em tempo de execução.Este é também um caso de despacho dinâmico, e, a principal razão da dinamicamente tipada línguas são geralmente mais lentos do que de linguagens estaticamente tipadas.

Algumas CPU (incluindo recente da Intel x86 fichas) usa a técnica chamada de especulativa de execução para utilizar o pipeline até mesmo sobre a situação.Apenas a busca de um caminho de execução.Mas a taxa de sucesso desta técnica não é tão alta.E a especulação sobre a falha faz com que o pipeline stall, que também faz enorme penalidade de desempenho.(isto é completamente pela CPU implementação.alguns móveis da CPU é conhecido como não esse tipo de otimização para poupar energia)

Basicamente, o C# é uma linguagem compilada estaticamente.Mas não sempre.Eu não sei condição exata e isso é inteiramente até a implementação de compilador.Alguns compiladores podem eliminar a possibilidade de distribuição dinâmica, impedindo método substituindo-se o método está marcado como sealed.Estúpido compiladores não pode.Este é o benefício de desempenho do sealed.


Esta resposta (Por que é mais rápido para processar um array ordenado de um indiferenciados matriz?) é descrever a previsão de ramificação muito melhor.

A marcação de uma classe sealed não deve ter nenhum impacto no desempenho.

Há casos em que a csc pode ter de emitir uma callvirt opcode em vez de um call código de operação.Parece, no entanto, esses casos são raros.

E parece-me que o JIT deve ser capaz de emitir a mesma não-chamada de função virtual para callvirt que seria para call, se ele sabe que a classe não tem qualquer subclasses (ainda).Se apenas uma implementação do método existe, não há nenhum ponto de carregar o seu endereço de uma vtable—ligue para a implementação de um diretamente.Para que o assunto, o JIT pode até mesmo incorporada a função.

É um pouco de um jogo, o JIT parte, porque se uma subclasse é depois de carregado, o JIT terão de jogar fora que o código de máquina e compilar o código novamente, emitindo-se uma verdadeira chamada virtual.Meu palpite é que isto não acontece na prática.

(E sim, VM designers realmente perseguir agressivamente esses pequenos desempenho wins).

Selados classes deve fornecer uma melhoria de desempenho.Uma vez que uma classe selada não pode ser derivada, de qualquer membros virtuais pode ser transformado em não-membros virtuais.

Claro, estamos falando de pequenos ganhos.Eu não marcar uma classe como vedada apenas para obter uma melhoria de desempenho, a menos de criação de perfil revelado, para que seja um problema.

<off-topic-rant>

Eu detesto selados classes.Mesmo se os benefícios de desempenho são surpreendentes (o que eu duvido), eles destruir o modelo orientado ao objeto, impedindo a reutilização através de herança.Por exemplo, a classe Thread está selado.Enquanto eu posso ver que alguém pode querer threads para ser tão eficiente quanto possível, também posso imaginar cenários em que ser capaz de subclasse de Thread teria grandes benefícios.Classe de autores, se você deve selo de suas aulas de "desempenho" razões, por favor, forneça uma interface pelo menos não temos de envolver-e-substitui em todos os lugares que nós precisamos de um recurso que você esqueceu.

Exemplo: SafeThread tinha de moldar a classe Thread porque o Thread é selada e não há IThread interface;SafeThread automaticamente armadilhas exceções não tratadas em tópicos, algo completamente ausente da classe Thread.[e não, a excepção não processada eventos não pegar exceções sem tratamento de threads secundárias].

</off-topic-rant>

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

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

a) Melhor tempo de compilação verifica (fundição para interfaces não implementado vai ser detectados em tempo de compilação, não só em tempo de execução)

e, de cima motivo:

b) Abuso de minhas aulas não é possível,

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

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

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

SSCLI (Rotor)
SSCLI:Fonte Compartilhada Common Language Infrastructure

O Common Language Infrastructure (CLI) é o padrão ECMA que descreve o núcleo da .NET Quadro.O código-Fonte Compartilhado CLI (SSCLI), também conhecido como Rotor, é um arquivo comprimido de código-fonte para um trabalho de implementação do ECMA CLI e da ECMA para C# idioma a especificação de tecnologias no coração da Microsoft .NET arquitetura.

selados as aulas vão ser pelo menos um pouco mais rápido, mas às vezes pode ser waayyy mais rápido...se o JIT Otimizador pode inline chamadas que de outra forma teria sido chamadas virtuais.Assim, onde há, muitas vezes chamadas de métodos que são pequenos o suficiente para ser lineares, definitivamente considerar a vedação de classe.

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

Eu sei que alguns aqui disseram que eles odeiam selado classes, porque eles querem a oportunidade de derivar de nada...mas que muitas VEZES não é o mais fácil de escolha...porque a exposição de uma classe para a derivação de bloqueios de você em muito mais do que não expor tudo isso.Seu semelhante a dizer "eu detesto classes que possuem membros privados...Muitas vezes eu não consigo fazer a classe fazer o que eu quiser porque eu não tenho acesso." O encapsulamento é importante...a selagem é uma forma de encapsulamento.

Executar este código, e você vai ver que selou as aulas 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 selada :00:00:00.1897568 NonSealed classe :00:00:00.3826678

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top