Feb 22, 2019

Aprendendo Mono.Cecil de uma maneira mais fácil

Read this post in english

A muito tempo atrás eu trabalhava com uma biblioteca para manipulação de bytecode Java chamada ASM. Um dos grandes diferenciais da mesma era sua excelente documentação bem como um aplicativo (asmifier) que, dado um trecho de código Java gerava um um programa com chamadas a API da biblioteca ASM que produzem bytecode equivalente ao do trecho de código usado.

Algum tempo depois (por volta de 2010 ~ 2011) o foco da minha carreira mudou para dotnet e eu comecei a usar uma biblioteca para manipulação de assemblies .NET (equivalente ao ASM) chamada Mono.Cecil e não demorou muito para eu entender que uma ferramente similar ao asmifier para o mundo .Net seria de grande valor.

Dada a complexidade inerente do problema (manipulação de MS IL), a documentação disponível precária do Mono.Cecil e meu interesse em aprender uma tecnologia emergente (naquela época) chamada MS Roslyn e assim nasceu Cecilifier.

Por razões que não são importantes agora eu acabei por diminuir o ritmo do mesmo algumas vezes mas nunca o abandonei por completo; ao contrário, o projeto progrediu de uma forma muito, muito, muito lenta com algumas paradas durante este período (veja o diagrama abaixo de você estiver curioso):


A boa notícia é que ano passado eu retomei o mesmo, adicionando algumas funcionalidades, corrigindo bugs, reorganizando o código, etc. Com isso eu planejava esperar até que o código atingisse um nível maior de qualidade antes de torná-lo open source mas eu concluí que mesmo no estado atual ele pode ser bastante útil; dessa forma tornei o repositório git público e implementei/configurei um site web.

Mesmo entendendo que este site torne o projeto mais acessível e útil atualmente o mesmo esta hospedado no gcp consumindo créditos gratuitos oferecidos pelo Google. Infelizmente estes créditos se esgotarão rapidamente, assim sendo, para permitir que o site continue disponível eu lancei uma campanha no Patreon com o objetivo de cobrir os custos de hospedagem, obter um domínio, etc. Outra formade contribuir é enviando bitcoins para 34e55WVni9nMFCiugo8d1bfwu9pLNzPH6n :)

Caso o montante mensal arrecadado não seja suficiente para cobrir tais custos eu irei remover o site mas o desenvolvimento deverá continuará, dependendo apenas do interesse tanto da comunidade como o meu :)

Se você gostou do projeto e vê o site como uma parte importante considere tornar-se um patrão. É claro que toda e qualquer forma de contribuição é bem vinda. Sinta-se a vontade em reportar problemas, enviar Merge Requests, ajudar outros usuários, adicionar mais testes, etc.

Certamente o site é uma das partes que eu dediquei menor esforço (mesmo porque este foi o recurso adicionado mais recentemente). Assim sendo esta é uma parte do projeto que se beneficiaria muito de desenvolvedores web com mais experiência.
Se desejar você pode ler mais detalhes a respeito do projeto (incluindo lista de  funcionalidades, limitações entre outras) no README incluso no repositório.

Finalizando envie-me uma mensagem no twitter contando se você esta utilizando o projeto, o que você acha, sugestões, comentários, etc (é sempre bom saber que o meu esforço esta sendo útil).

Espero que o mesmo seja útil.

Divirtam-se.

Adriano

Making it easier to getting started with Mono.Cecil

Leia este post em Português

A long time ago I was working with byte code manipulation in the black side of the force, i.e, in Java ;) using a library called ASM. In order to learn how to use this library (and also help raising developer productivity) one could use a tool called asmifier (and the related Bytecode Outline Eclipse plugin) which is able to take Java code snippets and generate calls to the ASM library to produce the equivalent byte code of the compiled code snippet.

Later, when I focused on dotnet development and started using Mono.Cecil to implement various tools to manipulate .Net assemblies (around 2010 ~ 2011) it did not took me too long to realize that something similar to asmifier in the .Net world could be of great value; add to that the implicit complexity of the problem domain, the sparse documentation available for Mono.Cecil and my interest in learning how to use a cool, emerging technology (at that time) called MS Roslyn and Cecilifier was born.

Due to reasons that are not important I've put the project aside a couple of times but never gave up or fully abandoned it.. instead it progressed very, very, very slowly with some stall periods in its development (check timeline bellow if you are curious):


The good news is that last year I resumed the work, added some missing features and fixed some bugs. I was planning to wait the code to reach some level of maturity before open sourcing it but I came to the conclusion that even in its current state it may be useful so I've finally opened its git repo and deployed a site.

As of today this web site is hosted in gcp, running of free credits; in order to keep it running (not necessarily in gcp) I've started a campaign in Patreon; in case this endeavor proves unsuccessful (i.e, it does not rise enough money) I'll shutdown the gcp server but development of the project is only tied to interest of both the community and my own. So if you think the web site is useful, please, consider donating. Another way you you can contribute is by sending bitcoins to 34e55WVni9nMFCiugo8d1bfwu9pLNzPH6n :)

Depending on the amount of money I raise monthly, I'll consider getting a custom domain; the excess will be used to cover hardware & beer costs as well as an incentive to put more effort in the development of the project.

[EDIT]

I removed the patreon option (didn't get any :) but you can still support me if you want to, through github sponsor

[/EDIT]


Of course you can also contribute by reporting issues, sending merge requests, engaging in discussions, helping users, etc.

The website is the part of the project to which I have dedicate the least amount of time/energy (it was one the last additions also) so it certainly could use some love :). If you are a web developer and would like to help, drop me a line.

If you use it, please drop me a message in twitter and tell me what you think, what improvements you would like to see, etc.

Finally, you can find more details (including features, limitations and more) in the README file in the git repo.

Have fun.

Adriano

Feb 6, 2019

Éviter les copies de données cachées en C#

Read this post in English

Leia este post em Português

Je voulais remercier tout ceux qui m'ont aidé avec la correction de ce texte (sans ordre particulier) : Charles Beauchemin, Vladimir Nachbaur.


Est ce que vous voyez un problème dans l’extrait de code ci-dessous ? Indice : Il y en a au moins un, lié à l’utilisation de Value Types & la performance.


D’accord, rendons le problème un peu plus évident en introduisant une erreur, laquelle, en supposant que le code en question a des tests unitaires, fera probablement remarquer le comportement immédiatement:


Quel serait le résultat maintenant ?
Si vous avez répondu :
M() : 0
M() : 1
M() : 2

Vous serez probablement surpris de voir :

M() : 0
M() : 0
M() : 0

C'est maintenant évident que quelque chose n’est pas correct.

Le problème est que le champ s a eté déclaré readonly, ce qui fait que le compilateur publie des copies défensives avant chaque appel de méthode sur ce type (dans notre cas s.M()); ça signifie que la méthode M() va ajouter le champ counter en une copie de la structure originale (au lieu de s), ce qui explique le comportement observé.

Notez que dans la première version du code (dans lequel la structure n’est pas modifiable), il n'est pas facile de détecter que quelque chose n'est pas correcte. Même dans la dernière version du code ce n'est pas clair pourquoi on n’observe jamais l’incrément de s.counter.

Pour comprendre ce qui se passe, examinons le code IL généré pour la fonction Main() (vous pouvez utiliser le site sharplab.io pour expérimenter avec le code) :


Maintenant, c’est un peu plus facile de remarquer les copies supplémentaires qui se produisent dans les offsets IL_0001 & IL_0006, puis IL_000f & IL_0014, et enfin IL_001d & IL_0022. Notez qu’après la copie du contenu de s dans la variable située dans l’emplacement 1, cette adresse est chargée sur la pile et le méthode M() est appelée.

Malheureusement, avec C#, c’est relativement facile de tomber dans d’autres “pièges” ; le prochain exemple montre deux autres cas : les paramètres in et les variables locales ref readonly:

La question naturelle qui vient est : Est-ce que c’est possible de s’éviter cette copies ? Si oui, comment ? La réponse dépend de la version du langage C# que vous utilisez :
  • < 7.3: Le seul moyen que je connaisse est de supprimer le modificateur readonly du champ (s dans notre exemple)
  • >= 7.3: Vous pouvez déclarer sa struct comme readonly afin de s’assurer que la même n’est pas modifiable donc le compilateur n'a pas à émettre de copies défensives (au cas où vous essaierait de modifier l’état de la structure, le compilateur émettra une erreur).
Il reste une question : à quel point ces copies sont-elles mauvaises? À mon avis la réponse dépend de comment elles affectent ton programme : la performance (i) et/ou comportement (ii) et aussi leurs exigences de performance.

Dans le cas de comportements incorrects, je crois que le consensus est que le développeur doit trouver et corriger des copies ; déjà dans les cas où ces copies n’affectent pas la performance, je dirais que le développeur doit analyser les avantages/inconvénients avant d'investir le temps pour se retrouver/corriger ces copies. Cela étant dit, si vous utilisez Visual Studio vous pouvez installer le plugin ErrorProne.NET que facilitera beaucoup la localisationde ces problèmes (Jetbrains Rider a un plugin intégré pour faire la même chose).

Finalement, si vous êtes intéressé à mieux comprendre la plate-forme.Net , je vous recommande de suivre ce blog

Amusez-vous,

Adriano

Evitando cópias de dados em C#

Read this post in English

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).
Resta uma questão: quão ruim estas cópias extras são? Na minha opinião a resposta depende de como as mesmas afetam seu programa; performance (i) e/ou comportamento (ii) e também dos seus requisitos quanto a performance.

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