Desempenho de métodos estáticos vs métodos de instância


108

Minha pergunta é relacionada às características de desempenho de métodos estáticos vs métodos de instância e sua escalabilidade. Suponha, para este cenário, que todas as definições de classe estão em um único assembly e que vários tipos de ponteiros discretos são necessários.

Considerar:

public sealed class InstanceClass
{
      public int DoOperation1(string input)
      {
          // Some operation.
      }

      public int DoOperation2(string input)
      {
          // Some operation.
      }

      // … more instance methods.
}

public static class StaticClass
{
      public static int DoOperation1(string input)
      {
          // Some operation.
      }

      public static int DoOperation2(string input)
      {
          // Some operation.
      }

      // … more static methods.
}

As classes acima representam um padrão de estilo auxiliar.

Em uma classe de instância, a resolução do método de instância leva um momento para ser oposta a StaticClass.

Minhas perguntas são:

  1. Quando manter o estado não é uma preocupação (nenhum campo ou propriedade é necessário), é sempre melhor usar uma classe estática?

  2. Onde houver um número considerável dessas definições de classe estática (digamos 100, por exemplo, com vários métodos estáticos cada), isso afetará o desempenho de execução ou o consumo de memória negativamente em comparação com o mesmo número de definições de classe de instância?

  3. Quando outro método dentro da mesma classe de instância é chamado, a resolução da instância ainda ocorre? Por exemplo, usando a palavra-chave [this] this.DoOperation2("abc")de dentro DoOperation1da mesma instância.



o que você quer dizer com "resolução de instância"? No nível IL, "este" ponteiro está disponível como qualquer outra variável local. Na verdade, em algumas versões CLR / JIT antigas, você poderia chamar um método de instância em um NULL, desde que ele não tocasse no 'this' - o código apenas voou e caiu em nada .. agora CLR / JIT contém null- verifique cada membro invocar ..
quetzalcoatl

> vijaymukhi.com/documents/books/ilbook/chap8.htm e 'chamar instância' versus apenas 'chamar'. O primeiro espera 'este' parâmetro e o último - não.
quetzalcoatl

@Quetzalcoatl desculpe a confusão, a questão era mais método para método da mesma instância e se isso requer que a instância seja resolvida por si mesma.
Bernie White

1
@quetzalcoatl Presumi que ele quisesse dizer, "o compilador se livra da verificação de que thisaponta para algo quando uma classe chama um método de instância em si mesma?"
Jon Hanna,

Respostas:


152

Em teoria, um método estático deve ter um desempenho ligeiramente melhor do que um método de instância, todas as outras coisas sendo iguais, por causa do thisparâmetro oculto extra .

Na prática, isso faz tão pouca diferença que ficará oculto no ruído de várias decisões do compilador. (Conseqüentemente, duas pessoas poderiam "provar" uma melhor do que a outra com resultados discordantes). Até porque o thisé normalmente passado em um registro e freqüentemente está naquele registro para começar.

Este último ponto significa que, em teoria, devemos esperar que um método estático que pega um objeto como parâmetro e faz algo com ele seja ligeiramente menos bom do que o equivalente como uma instância nesse mesmo objeto. Mais uma vez, porém, a diferença é tão pequena que, se você tentar medi-la, provavelmente acabará medindo alguma outra decisão do compilador. (Especialmente porque a probabilidade de essa referência estar em um registro o tempo todo também é bastante alta).

As diferenças reais de desempenho se resumem a se você tem objetos artificialmente na memória para fazer algo que deveria ser naturalmente estático, ou se você está enrolando cadeias de passagem de objetos de maneiras complicadas para fazer o que deveria ser naturalmente uma instância.

Portanto, para o número 1. Quando manter o estado não é uma preocupação, é sempre melhor ser estático, porque é para isso que serve a estática . Não é uma preocupação de desempenho, embora haja uma regra geral de jogar bem com otimizações de compilador - é mais provável que alguém se esforce para otimizar casos que surgem com uso normal do que aqueles que surgem com uso estranho.

Número 2. Não faz diferença. Há uma certa quantia de custo por classe para cada membro em termos de quantos metadados existem, quanto código existe no arquivo DLL ou EXE real e quanto código modificado haverá. É o mesmo, seja instância ou estático.

Com o item 3, thisé como thisfaz. No entanto, observe:

  1. O thisparâmetro é passado em um determinado registro. Ao chamar um método de instância dentro da mesma classe, ele provavelmente já estará naquele registro (a menos que tenha sido armazenado e o registro usado por algum motivo) e, portanto, não há nenhuma ação necessária para definir o thisque ele precisa ser definido para . Isso se aplica até certo ponto, por exemplo, os dois primeiros parâmetros do método sendo os dois primeiros parâmetros de uma chamada que ele faz.

  2. Como ficará claro que thisnão é nulo, isso pode ser usado para otimizar chamadas em alguns casos.

  3. Uma vez que ficará claro que thisnão é nulo, isso pode tornar as chamadas de método embutidas mais eficientes novamente, já que o código produzido para falsificar a chamada do método pode omitir algumas verificações de nulo que podem precisar de qualquer maneira.

  4. Dito isso, cheques nulos são baratos!

É importante notar que métodos estáticos genéricos atuando em um objeto, em vez de métodos de instância, podem reduzir alguns dos custos discutidos em http://joeduffyblog.com/2011/10/23/on-generics-and-some-of- the-associated-overheads / no caso em que determinado estático não é chamado para um determinado tipo. Como ele coloca, "Como um aparte, os métodos de extensão são uma ótima maneira de tornar as abstrações genéricas mais pagas".

No entanto, observe que isso se refere apenas à instanciação de outros tipos usados ​​pelo método, que de outra forma não existem. Como tal, ele realmente não se aplica a muitos casos (algum outro método de instância usou esse tipo, algum outro código em outro lugar usou esse tipo).

Resumo:

  1. Principalmente os custos de desempenho de instância vs estático estão abaixo de insignificantes.
  2. Os custos geralmente virão quando você abusar da estática, por exemplo, ou vice-versa. Se você não fizer parte de sua decisão entre estático e instância, é mais provável que obtenha o resultado correto.
  3. Existem raros casos em que métodos genéricos estáticos em outro tipo resultam em menos tipos sendo criados do que métodos genéricos de instância, o que pode fazer com que às vezes tenha um pequeno benefício por ser raramente usado (e "raramente" se refere a quais tipos são usados ​​no tempo de vida do aplicativo, não com que frequência é chamado). Depois de entender o que ele está falando naquele artigo, você verá que é 100% irrelevante para a maioria das decisões estáticas versus instâncias. Edit: E na maior parte só tem esse custo com ngen, não com código jitting.

Edit: Uma nota sobre o quão baratos são os cheques nulos (que afirmei acima). A maioria das verificações de nulo no .NET não verifica nada em relação a nulo, em vez disso, continua o que faria com a suposição de que funcionará e, se ocorrer uma exceção de acesso, ela será transformada em um NullReferenceException. Como tal, principalmente quando conceitualmente o código C # envolve uma verificação de nulo porque está acessando um membro da instância, o custo se for bem-sucedido será, na verdade, zero. Uma exceção seriam algumas chamadas inline, (porque eles querem se comportar como se tivessem chamado um membro da instância) e eles apenas acessam um campo para acionar o mesmo comportamento, então eles também são muito baratos e ainda podem ser deixados de fora de qualquer maneira (por exemplo, se a primeira etapa do método envolvia acessar um campo como ele era).


Você pode comentar se a questão estática vs instância tem alguma relação com a coerência do cache? É mais provável que um ou outro cause perda de cache? Existe um bom esboço explicando por quê?
scriptocalypse

@scriptocalypse Na verdade não. O cache de instrução não verá nenhuma diferença e, nesse nível, não há muita diferença entre acessar dados por thismeio de um parâmetro explícito. Um impacto maior aqui seria o quão próximos os dados estão de dados relacionados (campos de tipo de valor ou valores de array estão mais próximos do que dados em campos de tipo de referência) e padrões de acesso.
Jon Hanna

"em teoria, devemos esperar que um método estático que pega um objeto como parâmetro e faz algo com ele seja um pouco menos bom do que o equivalente como uma instância nesse mesmo objeto." - Você quer dizer que se o método de amostra acima leva parâmetro como objeto em vez de string, não estático é melhor? por exemplo: eu tenho meu método estático pega objeto como parâmetro e serializa em string e retorna string. você está sugerindo usar não estático neste caso?
batmaci

1
@batmaci, quero dizer, há uma boa chance de obj.DoSomehting(2)ser um pouco mais barato do que, DoSomething(obj, 2)mas como eu também disse, a diferença é tão pequena e depende de coisas minúsculas que podem acabar diferentes na compilação final que realmente não vale a pena se preocupar. Se você está fazendo algo tão caro (em relação ao tipo de diferenças em jogo aqui) como serializar algo em uma corda, então é especialmente inútil.
Jon Hanna

Há uma coisa, talvez óbvia, mas importante, faltando nessa resposta excelente: um método de instância requer uma instância, e criar uma instância não é barato. Mesmo um padrão ctorainda requer a inicialização de todos os campos. Assim que você já tiver uma instância, esta resposta se aplica ("todas as outras coisas sendo iguais"). Obviamente, um cctormétodo caro também pode tornar os métodos estáticos lentos, mas isso é apenas na primeira chamada e se aplica igualmente aos métodos de instância. Consulte também docs.microsoft.com/en-us/previous-versions/dotnet/articles/…
Abel

8

Quando manter o estado não é uma preocupação (nenhum campo ou propriedade é necessário), é sempre melhor usar uma classe estática?

Eu diria que sim. Ao declarar algo, staticvocê declara uma intenção de execução sem estado (não é obrigatório, mas uma intenção de algo que seria de esperar)

Onde houver um número considerável dessas classes estáticas (digamos 100, por exemplo, com vários métodos estáticos cada), isso afetará o desempenho da execução ou o consumo de memória negativamente em comparação com o mesmo número de classes de instância?

Não pense assim, a menos que você tenha certeza de que as classes estáticas são realmente contínuas, porque senão é fácil bagunçar as alocações de memória e obter vazamentos de memória.

Quando a palavra-chave [this] é usada para chamar outro método dentro da mesma classe de instância, a resolução da instância ainda ocorre?

Não tenho certeza sobre esse ponto (este é um detalhe puramente de implementação do CLR), mas acho que sim.


Métodos estáticos não podem ser simulados, se você fizer TDD ou mesmo apenas teste de unidade, isso vai prejudicar muito seus testes.
trampster

@trampster Por quê? É apenas um pedaço de lógica. Você pode facilmente zombar do que você dá? Para obter um comportamento correto. E muitos métodos estáticos serão partes privadas da lógica em uma função de qualquer maneira.
M. Mimpen

@ M.Mimpen contanto que você deixe isso para pequenas partes privadas, você está bem, se for um método público e você usar de outros fechamentos e precisar alterar o que ele faz em seu teste, então você travará, coisas como IO de arquivo ou acesso ao banco de dados ou chamadas de rede etc, se colocadas no método estático se tornarão unmockable, a menos que, como você diz, você injete uma dependência mockable como um parâmetro para o método estático
trampster

-2

métodos estáticos são mais rápidos, mas menos OOP, se você for usar padrões de design, método estático provavelmente código ruim, para escrever lógica de negócios melhor sem estática, funções comuns como leitura de arquivo, WebRequest etc. responda


16
Você não deu argumentos para suas reivindicações.
ymajoros

2
@ fjch1997 2 upvoters parecem pensar de outra forma (pelo que vale a pena). Comentar votos
negativos
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.