Lire ce post en Francais
Você nota algum problema no trecho de código abaixo? Dica: existe pelo menos um, relacionado ao uso de Value Types & performance.
Ok, vamos tornar o problema um pouco mais óbvio introduzindo um erro, o qual, assumindo-se que o código em questão possui testes unitários, provavelmente faria com que o comportamento fosse notado imediatamente. Qual seria o resultado do programa agora ?
Se você respondeu:
M() : 0
M() : 1
M() : 2
Você provavelmente ficará surpreso ao ver:
M() : 0
M() : 0
M() : 0
Agora ficou evidente que algo está errado.
O problema com este programa é que o campo s foi declarado como readonly o que faz com que o compilador emita cópias defensivas antes da chamada de qualquer método do tipo (no nosso caso s.M()); isso significa que o método M() incrementará o campo counter em uma cópia da estrutura original (ao invés de s) o que explica o comportamento observado.
Note que na primeira versão do programa (em que a estrutura em questão não é mutável) não é fácil detectar que algo pode não estar correto. Mesmo na última versão do programa não é claro o porquê nunca observamos o incremento de s.counter.
Para entender o que está ocorrendo vamos olhar o código IL gerado para o método Main() (você pode usar o sharplab.io para experimentar com o código):
Agora ficou mais fácil observar as cópias extras que ocorrem nos offsets IL_0001 & IL_0006 e depois em IL_000f & IL_0014 e finalmente em IL_001D & IL_0022. Note que após copiar o conteúdo de s para a variável local localizada no slot 1 o endereço da mesma é carregado na pilha e a seguir o método M() é executado.
Infelizmente em C# é relativamente fácil cair em outras “armadilhas”; O exemplo abaixo apresenta mais dois casos: parâmetros in e variáveis local ref readonly:
A pergunta natural que surge é: é possível evitar estas cópias? Como? A resposta depende da versão da linguagem que você está usando:
- < 7.3: A única maneira que conheço é remover o modificador readonly do campo (s no nosso exemplo)
- >= 7.3: Neste caso você pode declarar sua struct como readonly de forma a garantir que a mesma não é mutável; desta forma o compilador não precisará emitir cópias defensivas (caso o código tente modificar o estado da estrutura o compilador emitirá um erro).
No caso de comportamento incorreto acredito ser consenso que o desenvolvedor deve encontrar/corrigir estas cópias; já em cenários em que tais cópias afetam apenas a performance eu diria que o desenvolvedor deve analisar os prós/contras antes de investir tempo para encontrar/remover tais cópias (para um número de aplicativos o impacto em performance causado por estas cópias extras é desprezível). Dito isso, se você desenvolve usando o Visual Studio você pode instalar o ErrorProne.NET que irá facilitar em muito a localização de problemas deste tipo.
Finalmente, se você se interessa em entender a plataforma .Net mais a fundo recomendo acompanhar este blog
Have fun!
Adriano
No comments:
Post a Comment