Até pouco tempo atrás eu nunca tinha ouvido falar sobre o go-chi. Foi durante uma reunião de trabalho que o Marcos Filho comentou sobre. Na mesma hora eu já anotei aqui na lista de idéias de posts para o blog para fazer uma comparação entre ele e o gorilla/mux.
Meus testes foram basicamente escrever um simples server http com a rota /{name}
. Nesse server utilizei a ferramenta wrk para um teste de carga. Também escrevi um benchmark da própria linguagem para verificar, além de quanta porrada ele aguenta, o quanto de recurso os routers consumem.
Para fica mais simples, vou separar as comparações em três partes, onde nas duas primeiras vou apresentar os resultados individuais e por fim uma conclusão.
Então, para começar, vamos ver os resultados do router mais famoso do mundo Go.
Gorilla Mux
Primeiramente, vamos começar pelo código escrito para testar o queridinho Gorilla/mux.
package main import ( "encoding/json" "fmt" "net/http" "github.com/gorilla/mux" ) func main() { r := mux.NewRouter() r.HandleFunc("/{name}", GorillaHelloWorld).Methods("GET") http.ListenAndServe(":3000", r) } func GorillaHelloWorld(w http.ResponseWriter, rq *http.Request) { vars := mux.Vars(rq) w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{ "message": fmt.Sprintf("Olá %s!!!", vars["name"]), }) }
Com o servidor escrito, levantei ele com um go run
e depois executei o seguinte comando para fazer um teste de carga.
$ wrk -t12 -c400 -d30s http://localhost:3000/Tiago+Temporin
O resultado para essa execução foi o seguinte:

Ou seja, conseguimos atender 477.072 requests em 30s, uma média de 15.871 request/sec.
Com essa informação na mão, escrevi um pequeno benchmark para medir quanto recurso ele consome para conseguir chegar a esses números.
func BenchmarkGorilla(b *testing.B) { for n := 0; n < b.N; n++ { req, err := http.NewRequest("GET", "/Tiago", nil) if err != nil { b.Fatal(err) } rr := httptest.NewRecorder() router := mux.NewRouter() router.HandleFunc("/{name}", GorillaHelloWorld).Methods("GET") router.ServeHTTP(rr, req) } }
Para testa-lo, executei um go test -bench=. -cpu=8 -benchmem -benchtime=5s -count 5
, que gerou o seguinte resultado:

Como podemos ver no resultado, o número de Bytes/Operação e Alocações/Operação (duas últimas colunas) se mantiveram iguais em todas as execuções.
Chi
Assim como fizemos para o Gorilla/mux, vamos iniciar com o código que foi escrito para o server.
package main import ( "encoding/json" "fmt" "net/http" "github.com/go-chi/chi/v5" ) func main() { r := chi.NewRouter() r.Get("/{name}", ChiHelloWorld) http.ListenAndServe(":3000", r) } func ChiHelloWorld(w http.ResponseWriter, r *http.Request) { name := chi.URLParam(r, "name") w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{ "message": fmt.Sprintf("Olá %s!!!", name), }) }
Como podemos ver, assim como o do Gorilla/mux, um server bem simples para responder um “Olá {name}!!!”.
Com a implementação feita, depois foi só executar o go run
e o mesmo comando do wrk para o teste de carga. O resultado foi o seguinte:

Como podemos ver no print, o Chi conseguiu atender 531.159 requests em 30s com uma média de 17.648 request/sec.
Agora, vamos ver o código utilizado para realizar o benchmark.
func BenchmarkChi(b *testing.B) { for n := 0; n < b.N; n++ { req, err := http.NewRequest("GET", "/Tiago", nil) if err != nil { b.Fatal(err) } rr := httptest.NewRecorder() router := chi.NewRouter() router.Get("/{name}", ChiHelloWorld) router.ServeHTTP(rr, req) } }
Na sequência, executei o mesmo comando de benchmark que foi executado para o gorilla/mux. O resultado foi o seguinte:

Assim como podemos ver no primeiro benchmark, o Chi também manteve a mesma quantidade de Bytes/Operações e Alocações/Operações em cada um dos testes executados.
Conclusão
Depois de executar todos esses testes, podemos ver uma clara vantagem para o router Chi.
Em relação ao teste de carga, o Chi conseguiu atender cerca de 11% mais requests.
Na questão do benchmark que fizemos, o Chi utilizou apenas 47% da quantidade de Bytes/Operação e fez 1/3 das alocações de memória por operação quando comparado com o gorilla/mux, o que na minha opinião é uma BAITA diferença.
Em resumo, Chi se mostrou ser um router com uma performance superior consumindo menos recursos, ou seja, o ideal dos mundos.
Deixem suas dúvidas nos comentários.
Até a próxima!
Subscreva
Fique por dentro de tudo o que acontece no mundo Go.
[…] de escrever APIs. Porém, desde que fiz o post sobre benchmark comparando gorilla/mux e go-chi (link para o post), meu router favorito tem sido o go-chi, pois sua performance é bem superior. E para ajudar, […]