Skip to content

.Net Core 2.1 mais performático que o Go? Sim, veja um caso real

No artigo de hoje vou mostrar um caso real onde o .Net Core superou o Golang e claro para provar isso vou mostrar alguns benchmarks.

Vou começar este artigo informando que gosto muito do Golang e que esse artigo não está afirmando que uma linguagem é melhor que a outra. Apenas mostrando um caso real onde uma superou a outra naquele contexto. Veja, eu escrevi NAQUELE CONTEXTO e não É A MELHOR. Resumindo, .Net Core superou o Golang em um contexto.

Em uma aplicação legada onde tive que dar manutenção, tivemos que criar um serviço externo em container para melhor escalar, já que essa parte não estava conseguindo ser executada a tempo. Esse serviço que criamos ficou responsável por extrair o texto de imagens armazenados na aplicação. Isso é conhecido como OCR e usamos uma lib do Google chamada Tesseract. Essa lib é open source e se você gostou da ideia acesse https://github.com/tesseract-ocr/tesseract.

Como em microserviços, onde temos a liberdade de escolher a melhor linguagem para aquele contexto, resolvemos criar o serviço em Golang. Mas por que essa linguagem? Como muitos já sabem, Golang é extremamente rápida e é uma linguagem que roda nativamente. Outro coisa que nos fez levar a essa linguagem é que o Tesseract (a lib externa da Google) é executado por linha de comando e isso nos fez crer que isso nos daria uma excelente performance.

Vou ser sincero, antes de formalizar no Golang, fizemos em Node e Java e o resultado foi muito abaixo do esperado. Isso significa que Node e Java são ruim? Claro que não! Só não deu certo para aquilo que estávamos querendo.

Mas o que esse serviço faz? Bom, ele é bem simples. O código você verá mais abaixo, mas em resumo é uma API que recebe a url da imagem. O serviço faz o download localmente, executa o Tesseract pelo terminal para obter o texto, deleta a imagem gravada e retorna o texto para o cliente. Abaixo você vê o código em GO e a criação da sua imagem no dockerfile.

https://github.com/RafaelFTeixeira/tesseract

Legal, mas quando entrou o .Net Core?

Bom, em sábado de boa em casa, pensei em criar o mesmo serviço em .Net Core para saber se sua performance seria melhor. Não estava botando muita fé, mas mesmo assim, corajoso e sempre amando o .Net Core resolvi fazer. E para minha surpresa, Chupa! .Net Core superou o Golang. Abaixo o link para o código em .Net Core

https://github.com/StephanyBatista/ExtractOcrApi

Para os testes eu resolvi executar em containers, abaixo o código para levantar cada imagem:

docker run -d -p 3000:80 salmeidabatista/extractocrapi:v1.0

docker run -d -p 3001:3000 rafaelteixeira/tesseract:v1.0

E para saber se tudo estava ok executei no browser as seguintes as páginas http://localhost:3000 e http://localhost:3001. Conformem podem ver, levantei as imagens nas portas 3000 e 3001, uma do .Net Core e outra do Go.

Abaixo segue o log da ferramenta de benchmark Bombardier em Go e .Net Core

Go

>bombardier -c 5 -n 40 http://localhost:3001/?image=https://image.slidesharecdn.com/portugus2b-170225215804/95/texto-verbal-e-noverbal-13-638.jpg
Bombarding http://localhost:3001/?image=https://image.slidesharecdn.com/portugus2b-170225215804/95/texto-verbal-e-noverbal-13-638.jpg with 40 request(s) using 5 connection(s)
40 / 40 [===========================================================================================================================] 100.00% 31s
Done!
Statistics Avg Stdev Max
Reqs/sec 1.33 8.71 127.40
Latency 3.84s 2.69s 10.01s
HTTP codes:
1xx – 0, 2xx – 38, 3xx – 0, 4xx – 0, 5xx – 0
others – 2
Errors: the server closed connection before returning the first response byte. Make sure the server returns ‘Connection: close’ response header before closing the connection – 2

.Net Core

>bombardier -c 5 -n 40 http://localhost:3000/ocr?url=https://image.slidesharecdn.com/portugus2b-170225215804/95/texto-verbal-e-noverbal-13-638.jpg&type=jpg
Bombarding http://localhost:3000/ocr?url=https://image.slidesharecdn.com/portugus2b-170225215804/95/texto-verbal-e-noverbal-13-638.jpg with 40 request(s) using 5 connection(s)
40 / 40 [===========================================================================================================================] 100.00% 14s
Done!
Statistics Avg Stdev Max
Reqs/sec 2.91 12.00 99.91
Latency 1.66s 330.71ms 3.58s
HTTP codes:
1xx – 0, 2xx – 40, 3xx – 0, 4xx – 0, 5xx – 0
others – 0
Throughput: 2.27KB/s

Resumo do Bombardier

.Net Core superou o Golang, conseguiu executar em 14s para 5 conexões e 40 requisições. Fez as 40 e não teve erro enquanto que o Go fez em 31s e obteve 2 erros.

Note que a média de requisições do Go foi menor que a .Net Core, mas mesmo assim .Net Core foi melhor.

Abaixo segue um novo log da ferramenta de benchmark WRK em Go e .Net Core

.Net Core

>wrk -t5 -c7 -d10s http://localhost:3000/ocr?url=https://image.slidesharecdn.com/portugus2b-170225215804/95/texto-verbal-e-noverbal-13-638.jpg&type=jpg
Running 10s test @ http://localhost:3000/ocr?url=https://image.slidesharecdn.com/portugus2b-170225215804/95/texto-verbal-e-noverbal-13-638.jpg
5 threads and 7 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.63s 110.96ms 1.82s 53.85%
Req/Sec 0.00 0.00 0.00 100.00%
28 requests in 10.01s, 17.45KB read
Socket errors: connect 0, read 0, write 0, timeout 2
Requests/sec: 2.80
Transfer/sec: 1.74KB

Go

>wrk -t5 -c7 -d10s http://localhost:3001/?image=https://image.slidesharecdn.com/portugus2b-170225215804/95/texto-verbal-e-noverbal-13-638.jpg
Running 10s test @ http://localhost:3001/?image=https://image.slidesharecdn.com/portugus2b-170225215804/95/texto-verbal-e-noverbal-13-638.jpg
5 threads and 7 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.85s 63.55ms 1.95s 73.33%
Req/Sec 0.00 0.00 0.00 100.00%
23 requests in 10.01s, 14.38KB read
Socket errors: connect 0, read 0, write 0, timeout 8
Requests/sec: 2.30
Transfer/sec: 1.44KB

Resumo do WRK

Agora eu quis testar com uma nova ferramenta e com um tempo limitado, 5 threads, 7 conexões e 10s. O resultado foi o mesmo, .Net Core superou o Golang e conseguiu na média ter 28 requisições contra 23 do Go, sendo que o Go teve 8 timeout e o .Net Core apenas 2.

Veja que estou criando um determinado número de usuários para serem executados em paralelos com N requisições cada um. Esse tipo de teste me agrada bastante, por que nem sempre a linguagem que atende mais rápido significa que será a melhor em produção. A linguagem que melhor saber trabalhar em produção com concorrência, bloqueio de thread e baixo consumo de memória é que melhor vai atender o nosso caso.

O que pode ter feito o .Net Core ganhar?! Bom, isso é difícil falar, mas acredito que a forma com que o .Net Core trabalha com multi-thread foi o que fez a diferença. As partes mais complicadas do código, como: baixar a imagem, executar o tesseract e deletar a imagem, fiz de forma assíncrona, isso significa que a thread atual que está atendendo não espera esses processos terminarem, ela “põe isso de lado” e vai atender outra requisição, quando o processo inicial termina, a thread volta a atender a requisição inicial. Isso também acontece no Go, mas de alguma forma para esse contexto o .Net Core foi melhor.

Caracas quem diria, .Net Core ganhou! Não me leve a mal, mas isso mostra o que os engenheiros da Microsoft mostraram lá em 2014 quando o beta foi lançado. O .Net Core foi criado do zero e uma das coisas mais buscadas ao longo desses anos foi justamente a performance. Toda nova versão lançada sempre tem um item de melhoria de performance e com o .Net Core 2.1 não foi diferente pelo visto.

Mas o Golang não ganhou em nada? Não é bem assim, nesses dois serviços criados existe uma tela inicial explicando como utilizar o serviço, basicamente um tutorial para informar que o serviço está no ar e como usar e nisso o Go foi muito melhor. O Go teve através do WRK um resultado de 579680 requests in 10.10s, 106.70MB read, enquanto que o .Net Core teve no mesmo benchmark o resultado 169692 requests in 10.10s, 47.09MB read.

Continuando sobre o código que criei em .Net Core, adicionei mais libs para extrair texto não só de imagens mais também PDF e Docx. Se você precisar pode usar e fique a vontade para contribuir no projeto.

Bom galera, é isso. Eu queria compartilhar esse artigo com vocês e mostrar o que conseguimos com o .Net Core.

Gostaria muito de agradecer o Rafael Teixeira que fez uma aposta comigo de qual seria mais rápido, .Net Core ou Golang, e claro ele perdeu, mas com toda certeza nessa história toda só houve ganhadores, pois todos ganham com conhecimento novo. Esse tipo de aprendizado é muito importante para não aceitarmos uma verdade que pode não ser tão verdade assim em todos os casos.

Poxa, mais eu consigo melhorar esse código em Golang! Cara, por favor, sinta-se livre para discutir sobre isso, é muito legal esse tipo de conversa.  

Abraços!

Published in.Net Core

4 Comments

  1. Victor Hugo Gonçalves Leal de Souza Victor Hugo Gonçalves Leal de Souza

    O título realmente chama a atenção, bem polêmico, porém você esqueceu de considerar alguns cenários, então vamos lá. Primeiro, você realiza operações de IO (escrita e leitura de arquivo), no código .NET o seu FIleHelper tem todos os métodos async, enquanto no projeto em Go, você escreve tudo de forma síncrona, sem utilizar Go routines, só esse fato já torna esperado o código .NET ter um delta menor do que em Go, experimente utilizar Go routines para realizar as operações de IO e faça de novo o benchmark, creio que vai ser diferente (https://gobyexample.com/goroutines). Segundo, a parte que você lida com HTTP requests no código .NET também são métodos async, enquanto no código em Go você está utilizando o package http default que não tenho certeza, mas creio não ser async por default, troque por um framework como o gin (https://github.com/gin-gonic/gin) sendo o mais básico, ou o fasthttp (https://github.com/valyala/fasthttp) e compare novamente, creio que o resultado vai ser diferente também. O artigo só demonstra o quanto Go é performático, pois sem uso de async em nenhum dois casos, conseguiu ficar na metade do tempo de .NET utilizando async em quase tudo, com certeza existem casos onde .NET pode sim superar a perfomance de Go, mas o benchmark que você fez, não demonstra isso, por faltar considerar esses cenários! Abraços

    • stephanybatista stephanybatista

      Opa! Vou tentar sim, valeuu!

  2. Rafael Teixeira Rafael Teixeira

    Valeu pela dica. Vou fazer essas alterações

  3. Jefferson Jefferson

    Realmente um bom assunto, esta de parabéns pela iniciativa! Mas concordo com o commentario do colega Victor, você nao criou no Go da melhor forma possivel com tudo que ele le oferece. Se você realizar as alteraçao por favor poste novamente.
    E parabéns novamente!

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *