Concrete Logo
Hamburger button

Uma história para ferramentas de desempenho Elixir

  • Blog
  • 5 de Março de 2018
Share

Eu tenho desenvolvido Elixir desde a versão 0.12 e preguei sobre Erlang e o BEAM alguns anos antes…

E para colocar a mão na massa nas linguagens que construí/ contribuí em algumas bibliotecas (junit_formatter, joken, plug etc), participei de algumas discussões em listas/ fórum (lembre-se que sou dos velhos tempos), fiz uma apresentação no Elug (Elixir User Group de São Paulo) onde José estava presente (sobre uma API Plug com autenticação Joken e backend da Amnesia), li muitos livros Erlang e Elixir, treinei em um Elixir DOJO onde construímos o GenServer distribuído (parte 1 e parte 2, em português), Elixir koans, Exercício.io etc.

Mas o fato é: tenho quase nenhuma experiência de produção com a linguagem. Tenho bastante experiência em Java, no entanto até posso ver a grande diferença que o Elixir traz em termos de experiência do desenvolvedor. Isso realmente me causou muito impacto enquanto eu iniciava uma refatoração Joken.

Então lá eu estava, olhando para a fonte de um Joken. Ainda não foi lançado, mas queremos renovar a API porque, em nossa história de problemas, parece que não estamos ajudando nossos usuários tanto quanto pudermos. Além disso, houve muitas novas bibliotecas publicadas no Hex que fazem exatamente a mesma coisa e que também trouxeram alguma inspiração. Eu e Bryan Joseph pensamos em refatorar as coisas por um tempo, mas não conseguimos encontrar o tempo adequado.

Enfim, além da API pública, eu estava interessado em análise de desempenho. Podemos torná-lo mais rápido? Afinal, o desempenho é uma das desvantagens de usar JWTs em vez de Phoenix.Token. Foi quando comecei a aprofundar as ferramentas que temos.

Benchfella e Benchee

Joken já possui uma série de benchmark. Se instalou em Benchfella, uma biblioteca muito agradável que nos ajudou a configurar uma suíte real rapidamente. Não usei isso para examinar os detalhes, mas apenas para comparar diferentes algoritmos (HS256, RS256, ES256, PS256 etc). Inicialmente, também para entender o quão grande foi o impacto no desempenho que as versões erlang puras dos algoritmos tiveram.

If you are not in context, Andrew Bennett, the creator of the project erlang-jose, implemented pure Erlang versions of crypto algorithms that the VM didn’t have a native version for.

Mais recentemente eu estive olhando para o Benchee por causa do https://www.elixirbench.org/, o vencedor do spawnfest. Eles escolheram apoiar o Benchee primeiro, então eu queria tentar.

Na primeira vez que rodei, ele me deu os seguintes resultados:

Esta é uma boa visão geral e a biblioteca é realmente simples de usar. A principal diferença entre Benchee e Benchfella é a abordagem para a configuração da suíte. Usando Benchfella, usamos tarefas específicas de mistura e com Benchee usamos apenas mix run <bench_script>.exs. Prefiro a abordagem Benchfellas de separar este código e dar uma localização padrão para seus arquivos, mas é apenas uma questão de opinião. A API de Benchee parece ainda mais direta.

Ferramentas integradas Elixir

Depois dessa visão geral macro, queria entender o que usava mais tempo ‘in the call stack’: essas funções executam mais vezes e, como tal, são responsáveis ​​por um período maior de tempo em cada execução. Para fazer isso, temos várias ferramentas integradas em Elixir: mix profile.cprof, mix profile.eprof and mix profile.fprof. Como de costume, a documentação é realmente agradável quando é uma ferramenta incorporada da Elixir. Eu recomendo a todos que tentem rodar mix help profile.<task>.

No meu caso, usei: mix profile.fprof -e DefaultAuth.generate_and_sign:

PS: Não se incomode em ler o nome de cada função aqui, pois é tudo experimental.

Algo que notei imediatamente é que base64url é chamado muitas vezes! E se eu pudesse acelerar só um pouco, já significaria um grande impacto.

É importante notar uma coisa aqui: isso foi descoberto muito rapidamente com uma ferramenta embutida sem ter que procurar o código-fonte de uma dependência infantil.

A dependência em questão é composta por apenas um módulo.

O próximo passo foi testá-lo contra o módulo incorporado do Elixir. Mas como podemos substituir cada chamada na dependência de JOSE’s, por outra? Bem, como este era apenas um módulo, redefini o mesmo módulo dentro da minha própria lib assim:

Então disparei o Benchee novamente e:

Isso é ~ 5k mais ips e 22 micro segundos mais rápido que a média! Uau! Isso nos leva a 2x o desempenho da implementação atual!

Conclusão

Elixir é realmente muito bom quando se trata de ferramentas e bibliotecas. Tanto que rapidamente consegui:

  • Uma análise de desempenho da visão geral usando Benchfella ou Benchee. Ambos são bem diretos para conectar seu código;
  • Acompanhar os possíveis bottlenecks com built-in mix profile.fprof -e;
  • Substituir um módulo de dependência (apesar de deixar um aviso de redefinição de módulo durante a compilação);
  • Confirmar que foi positivo :);
  • Abrir um chamado sobre a configuração do módulo base64 (que pode beneficiar não só eu, mas todas as outras bibliotecas, dependendo de JOSE).

Eu venho de um background Java e não há nada tão direto quanto isso (sim… criei muitos códigos Java ao longo dos anos). Estou muito feliz com os resultados (48 micro segundos para gerar um JWT assinado com 4 reivindicações e algorítmos HS256). Claro, o desempenho é viciante e ainda estou testando outras coisas. 🙂

A minha mensagem aqui é: antes de olhar para como fazer uma função mais rápida, é importante dar um passo atrás e dar uma olhada no todo. Talvez otimizar uma função muito pequena possa ter um ótimo impacto no desempenho. A boa notícia é que o Elixir está trancado e carregado com ferramentas para fazer a felicidade do desenvolvedor com um ambiente oneroso muito rápido e competidor. Não poderia pedir mais 🙂

É desenvolvedor Android e quer trabalhar em um time ágil de verdade e multidisciplinar? Envie seu currículo para trabalheconosco@concrete.com.br.