Local Functions de dentro das trincheiras

Elemar Júnior

No post de hoje vamos entender um pouco como se comporta o código C# quando decidimos utilizar o recurso Local Functions.

Local Functions é uma recurso adicionado desde a versão do C# 7.0. Esta funcionalidade se comporta como uma função ou método privado e pode ser implementada e utilizada em várias partes do código, como por exemplo:

  • Métodos / Funções
  • Construtores
  • Métodos Anônimos
  • Expressões Lambda
  • Outras Local Functions

Mas ai você se pergunta: Por que eu deveria utilizar uma Local Functions quando eu posso implementar tudo no mesmo método ou, ainda, posso criar um método ou função privada dentro da minha classe para executar alguma tarefa?

Primeiro vamos entender o que é Closure, o que é Delegate Method ou Anonymous Method, Qual o comportamento de cada uma dessas estruturas e como o compilador resulta o código final e como esse código pode degradar ou não a performance da sua aplicação.


Closure

Closure é uma técnica que nos permite criar uma funcão dentro de outras funções ou métodos, no qual as referência das variáveis do contexto externo a Local Functions são aproveitadas junto com seus valores ou até mesmo um objeto pode ser passado como referência para ser utilizado dentro do contexto de uma Closure, no momento de sua criação da Closure.

Delegate Methods ou Anonymous Method são ponteiros de funções que especificam um método ou um objeto para realizar a execução do seu contexto. Normalmente no C#, você encontrará esse tipo de implementação em Callbacks ou Event Listeneres.


Agora que já entendemos o conceito sobre o que é Closure e Delegate Method, então…

Quando entendemos ser o momento adequado para utilizar uma Local Functions?

Em termos de utilização uma Local Functions pode parecer muito similar a um Delegate Method ou Anonymous Method e isso pode nos levar a enteder que utilizar esse novo recurso do C# 7.0, pode parecer uma solução redundante ao que já temos disponível anteriormente. No entanto, existem fatores restritivos na implementação de um Delegate Method e Anonymous Method, os quais nos levam as implicações de baixa performance, comparado ao uso de uma Local Functions.

Sobre criarmos métodos privados dentro da classe e utilizá-lo como método ou função de suporte ao contexto que precisamos executar? Bem, indo por essa abordagem — a depender do caso em que você se encontra — se esse método privado será somente utilizado como um helper para um outro método, exclusivamente, utilize Local Functions. Porquê? Por que utilizar um método privado fará chamadas a Virtual Method Table gerando custo computacional, onde teremos perda de performance por executar o procedimento de Method Lookup através das chamadas callvirt, conforme veremos a seguir.

Abaixo serão apresentados cenários de utilização e comparação de ambas as abordagens, mostrando como o código C# gerado, para cada um dos casos, é bastante diferente e o que isso implica na performance da sua aplicação. Todos resultados de decompilação, foram obtidos utilizando a ferramenta: sharplab.io.

1° Cenário — Utilizando Delegate Method

O trecho de código acima, para muitos, não tem nada de especial e pode parecer um tanto ingênuo, porém é o suficiente para esclarecer como o compilador do .net interpreta gera o código C#, conforme o artigo se propõe a mostrar. Mas agora observe que o próprio compilador Roslyn, através da IDE do Visual Studio, está sugerindo que troquemos a implementação de Delegate Method para Local Functions, conforme mostra a imagem abaixo. Mas porquê essa troca nos foi sugerida?

Sugestão do Visual Studio para trocar a implementação por uma Local Functions.

A mudança foi sugerida devido o código csharp gerado, ser igual ao código abaixo:

O código csharp gerado acima, cria uma classe “<>c”, a qual armazena uma referência ciclica para ela mesma, ou seja, neste momento começamos a requisitar espaço de armazenamento na HEAP (alto custo computacional). Um outro ponto interessante, é existe uma requisição para alocação de memória e armazenamento de um ponteiro, quando o código executa new Action<int, int>, mas o que acontece nesse momento?

Abaixo, observe o código gerado pela IL para esta ação:

Código IL (Intermediate Language) gerado pelo compilador.

Analisando a instrução da IL_0021, você vai observar que há uma chamada para uma instrução assembly, requisitando uma busca na Virtual Method Table (callvirt). A instrução callvirt executa o procedimento Method Lookup e ao encontrar o método requisitado o executa.

Esse procedimento existe, pois estamos trabalhando com objetos na HEAP, e o método em questão foi ligado, tardiamente, através da referência do objeto em memória. Isso tudo acontece na execução do método: System.Action`2<int32, int32>::Invoke(!0, !1). Devido a esse comportamento, utilizar Delegate Method vai levar sua aplicação a gastar mais tempo pra executar um trecho de código e vai necessitar alocar mais espaço na HEAP —um espaço ainda maior caso o Delegate Method estiver utilizando objetos grandes (LOH, ou seja, o tamanho seja igual ou superior a 85Kb ou 85.000 bytes).

2° Cenário — Utilizando Local Functions

Notem que declararimplementar e utilizar uma Local Functions é extremamente simples do ponto de vista da sintaxe do código e não há nenhuma novidade para utilizarmos este novo recurso. Observe, também, que uma Local Functions é declarada após a instrução return do método que a está chamando, no caso SumWithLocalFunction, separando a lógica principal do método de suporte (InternalSum()). Agora vamos analisar o código do código C# gerado pelo compilador Roslyn:

Através do código gerado pelo compilador, começam a aparecer técnicas e estratégias interessantes para redução do consumo de memória e otimização de código, ou seja, no código acima não temos alocação usando a área de memória HEAP (onde temos um alto custo de gestão para a estrutura computacional). A alocação de memória fica na Stack, devido a criação de uma Struct.

Uma Local Functions pode usar uma variável local, acessar parâmetrosdo método que a encapsula ou instâncias de outras classes e NÃO haverá alocação na HEAP, isso porque o compilador gera uma Struct especial, chamada Closure Struct, onde serão armazenados todos os valores que serão utilizados pela Closure Method, InternalSum(). Analisando o código gerado, podemos observar que a Struct é instanciada, todos os campos (fields) são inicializados com os valores dos parâmetros do método SumWithLocalFunction(…) e a struct é, então, passada como referência para um método estático, otimizando o acesso e uso das áreas de memória, bem como a execução do contexto geral. Perceba, também, que o compilador Roslyn gerou uma Struct com a configuração do Layout utilizando a opção Auto, o que faz a organização e otimização da alocação de memória da Struct ser automática (esse tema será assunto para um próximo post).

Abaixo temos um Benchmark mostrando o tempo de execução de ambas as abordagens:

Benchmark mostrando o tempo de execução de ambas as abordagens.

Conclusão

Com base no que vimos hoje, utilizar Local Functions em substituição — Quando for possível aplicar essa abordagem, tem sido uma prática aplicada dentro dos códigos que os times na Microsoft tem escrito e tem obtido bons resultados de performance. Tenho feito uso desse novo recurso nos códigos que venho implementando no meu dia a dia. Há uma série de outras nuânces sobre Delegate Method, Anonymous Method e Local Function as quais estão detalhadas em um artigo do Sergey Tepliakov, no qual tomei como base.

PS.: Tenho buscado conhecer, cada vez mais detalhes internos e entender como a linguagem CSharp se comporta ao gerarmos código C# compilado e, consequentemente, código IL (Intermediate Language). Acredito que faz parte de um dos skills de um desenvolvedor, buscar conhecer sempre um pouco mais a linguagem que escolheu trabalhar no seu dia a dia, pois é através desse conhecimento que podemos enteder como podemos escrever códigos, cada vez mais otimizados. Há cenários que Micro-Otimizações não são obrigatórias, mas quando a necessidade surgir, você precisará estar pronto pra saber aplicar a solução adequada.

Compartilhe este insight:

Comentários

Participe deixando seu comentário sobre este artigo a seguir:

Subscribe
Notify of
guest
0 Comentários
Inline Feedbacks
View all comments

AUTOR

Elemar Júnior
Fundador e CEO da EximiaCo atua como tech trusted advisor ajudando empresas e profissionais a gerar mais resultados através da tecnologia.

NOVOS HORIZONTES PARA O SEU NEGÓCIO

Nosso time está preparado para superar junto com você grandes desafios tecnológicos.

Entre em contato e vamos juntos utilizar a tecnologia do jeito certo para gerar mais resultados.

Insights EximiaCo

Confira os conteúdos de negócios e tecnologia desenvolvidos pelos nossos consultores:

Arquitetura de Dados

Insights de um DBA na análise de um plano de execução

Especialista em performance de Bancos de Dados de larga escala
Arquitetura de Software

Estratégias para modernização do legado

Desenvolvedor .NET/NodeJs e especialista em Kafka com experiência em startups e grandes empresas
Infraestrutura e Nuvem

Migração para a nuvem, mais do que mudança tecnológica, implica em mudança da cultura organizacional

Engenheiro de nuvem, arquiteto de software e especialista em Containers e Devops

Acesse nossos canais

Simplificamos, potencializamos e aceleramos resultados usando a tecnologia do jeito certo

EximiaCo 2022 – Todos os direitos reservados

0
Queremos saber a sua opinião, deixe seu comentáriox
()
x

Local Functions de dentro das trincheiras

Para se candidatar nesta turma aberta, preencha o formulário a seguir:

Condição especial de pré-venda: R$ 14.000,00 - contratando a mentoria até até 31/01/2023 e R$ 15.000,00 - contratando a mentoria a partir de 01/02/2023, em até 12x com taxas.

Tenho interesse nessa capacitação

Para solicitar mais informações sobre essa capacitação para a sua empresa, preencha o formulário a seguir:

Tenho interesse em conversar

Se você está querendo gerar resultados através da tecnologia, preencha este formulário que um de nossos consultores entrará em contato com você:

O seu insight foi excluído com sucesso!

O seu insight foi excluído e não está mais disponível.

O seu insight foi salvo com sucesso!

Ele está na fila de espera, aguardando ser revisado para ter sua publicação programada.

Tenho interesse em conversar

Se você está querendo gerar resultados através da tecnologia, preencha este formulário que um de nossos consultores entrará em contato com você:

Tenho interesse nessa solução

Se você está procurando este tipo de solução para o seu negócio, preencha este formulário que um de nossos consultores entrará em contato com você:

Tenho interesse neste serviço

Se você está procurando este tipo de solução para o seu negócio, preencha este formulário que um de nossos consultores entrará em contato com você:

× Precisa de ajuda?