Benchmark dos routers http: chi vs gorilla mux

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:

wrk – gorilla/mux

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:

benchmark – gorilla/mux

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:

wrk – go-chi

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:

benchmark – chi

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.

Um comentário sobre “Benchmark dos routers http: chi vs gorilla mux

Deixe uma resposta