Comparando a perfomance de Go, .NET e PHP

21 Agosto 2019

Com contribução de Felipe Finhane e Marcio Zacarias.

Cenários comparados

Utilizando o programa wrk, calculei a capacidade de requisições por segundo das seguintes variações de ambiente:

Cenário Linguagem Servidor Framework Docker Imagem Docker
Go Go Embutido Não Não N/A
.NET .NET Embutido Não Sim microsoft/dotnet:2.2-aspnetcore-runtime
PHP-1 PHP Embutido Não Não N/A
PHP-2 PHP Nginx Não Sim N/A
PHP-3 PHP Nginx Symfony Sim richarvey/nginx-php-fpm
PHP-4 PHP Nginx Symfony Sim (private)
Static N/A Nginx Não Sim nginxdemos/hello
Clojure Clojure JVM Não Não N/A

Todos os ambientes foram configurados para retornar uma única linha contendo repetições da string “Hello, world!”.

Os cenários Go, PHP-1, PHP-2 e Static estão rodando o mínimo de código possível que permita servir a resposta HTTP esperada.

Já os cenários .NET, PHP-3 e PHP-4 estão estruturados como projetos web MVC, sendo que o .NET está utilizando apenas bibliotecas padrões do .NET Core, e os PHP-3 e PHP-4 estão rodando o framework Symfony.

Clojure utiliza o projeto Leiningen para criação da estrutura e Pedestal como biblioteca de API.

Os ambientes acima foram disponibilizados no GitHub: https://github.com/rpagliuca/go-vs-dotnet-vs-php.

Resumo dos resultados

Os resultados foram os seguintes:

Cenário Requests por segundo
Go 30437
.NET 6933
PHP-1 135
PHP-2 3691
PHP-3 353
PHP-4 19
Static 9798
Clojure 1692

O Go venceu com folga, com 30.000 requests por segundo, já que além de ser uma linguagem compilada diretamente para código nativo, o servidor web está embutido no binário gerado.

O cenário Static (Nginx servindo uma página estática index.html) veio logo atrás, com quase 10.000 requests por segundo, sendo que a diferença provavelmente se dá pela necessidade de o servidor Nginx ler a página index.html a partir do filesystem.

O cenário .NET não ficou muito longe, com quase 7.000 requests por segundo. Esse resultado me surpreendeu positivamente, já que é um projeto bem mais complexo, estruturado no formato MVC, e rodando no runtime .NET Core dentro do Docker.

Clojure demostrou uma perfomace melhor que PHP (utilizando framework) mas não chegou a 20% da performace do .NET.

Por último, o PHP conseguiu apenas 50% da performance do .NET, mas apenas quando utilizando o cenário sem frameworks. Já quando utilizamos o Symfony, a performance caiu mais 10 vezes com a imagem richarvey/nginx-php-fpm, e ainda mais com uma imagem privada.

Esse último ponto serve como alerta: construir uma imagem Docker do zero, pode ser mais desafiador do que aparenta. Sempre faça benchmarks comparando suas imagens privadas contra outras imagens confiáveis disponíveis no Dockerhub. O resultado pode te surpreender (como me surpreendeu!).

Resultados completos:

Go

Running 1s test @ http://localhost:19319
  1 threads and 1 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    38.31us   67.33us   1.97ms   97.27%
    Req/Sec    30.62k     4.13k   32.91k    90.91%
  Latency Distribution
     50%   29.00us
     75%   31.00us
     90%   36.00us
     99%  257.00us
  33471 requests in 1.10s, 24.32MB read
Requests/sec:  30436.84
Transfer/sec:     22.12MB

.NET

Running 1s test @ http://172.24.0.2:5000
  1 threads and 1 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   158.51us  198.57us   4.70ms   98.15%
    Req/Sec     6.97k   561.31     7.49k    90.00%
  Latency Distribution
     50%  135.00us
     75%  143.00us
     90%  152.00us
     99%  652.00us
  6939 requests in 1.00s, 5.28MB read
Requests/sec:   6933.43
Transfer/sec:      5.28MB

PHP-1

Running 1s test @ http://localhost:19319
  1 threads and 1 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   219.61us   61.79us 786.00us   95.71%
    Req/Sec   136.80      5.14   144.00     80.00%
  Latency Distribution
     50%  222.00us
     75%  242.00us
     90%  266.00us
     99%  409.00us
  140 requests in 1.04s, 20.37KB read
  Socket errors: connect 0, read 140, write 0, timeout 0
Requests/sec:    135.02
Transfer/sec:     19.65KB

PHP-2

Running 1s test @ http://172.27.0.2
  1 threads and 1 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   369.61us  540.17us   6.59ms   93.66%
    Req/Sec     3.72k   383.31     4.17k    80.00%
  Latency Distribution
     50%  216.00us
     75%  302.00us
     90%  508.00us
     99%    3.16ms
  3697 requests in 1.00s, 2.98MB read
Requests/sec:   3691.39
Transfer/sec:      2.97MB

PHP-3

Running 1s test @ http://172.26.0.2
  1 threads and 1 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     3.23ms    2.86ms  28.50ms   94.91%
    Req/Sec   354.30     83.73   400.00     90.00%
  Latency Distribution
     50%    2.49ms
     75%    2.86ms
     90%    3.59ms
     99%   19.99ms
  353 requests in 1.00s, 302.66KB read
Requests/sec:    352.56
Transfer/sec:    302.27KB

PHP-4

Running 1s test @ http://172.28.0.2
  1 threads and 1 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    50.26ms   10.27ms  92.37ms   94.74%
    Req/Sec    19.00      3.16    20.00     90.00%
  Latency Distribution
     50%   47.57ms
     75%   48.16ms
     90%   51.25ms
     99%   92.37ms
  19 requests in 1.00s, 16.44KB read
Requests/sec:     18.98
Transfer/sec:     16.43KB

Static

Running 1s test @ http://172.23.0.2
  1 threads and 1 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   169.02us  472.01us   8.00ms   96.21%
    Req/Sec     9.85k   787.34    10.70k    80.00%
  Latency Distribution
     50%   88.00us
     75%  104.00us
     90%  138.00us
     99%    2.19ms
  9861 requests in 1.01s, 8.25MB read
Requests/sec:   9797.95
Transfer/sec:      8.19MB

Clojure

Running 1s test @ http://localhost:8080
  1 threads and 1 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   766.97us    1.13ms  13.80ms   95.05%
    Req/Sec     1.70k   417.51     2.06k    70.00%
  Latency Distribution
    50%  506.00us
    75%  672.00us
    90%    0.93ms
    99%    6.80ms
    1694 requests in 1.00s, 1.81MB read
Requests/sec:   1692.34
Transfer/sec:      1.81MB