Accélérez vos API

Feb 21 2023
La création de nouveaux objets peut être une opération relativement coûteuse, en particulier si elle implique l'allocation de mémoire sur le tas. La réutilisation d'objets peut donc aider à réduire la surcharge et à améliorer les performances. En conservant un pool d'objets préalloués et en les réutilisant au besoin, nous pouvons éviter la surcharge d'allocation et de désallocation de mémoire (le récupérateur de place désalloue les variables inutilisées dans la mémoire ).

La création de nouveaux objets peut être une opération relativement coûteuse, en particulier si elle implique l'allocation de mémoire sur le tas. La réutilisation d'objets peut donc aider à réduire la surcharge et à améliorer les performances. En conservant un pool d'objets préalloués et en les réutilisant au besoin, nous pouvons éviter la surcharge d'allocation et de désallocation de mémoire (le récupérateur de place désalloue les variables inutilisées dans la mémoire ).

Cela peut être particulièrement utile pour les API qui doivent gérer un grand nombre de requêtes, où la création et la destruction d'objets à chaque requête peuvent constituer un goulot d'étranglement important. En réutilisant des objets, nous pouvons réduire la quantité de temps CPU et de mémoire requise, ce qui peut conduire à de meilleures performances et à la capacité de gérer plus de requêtes.

Pour ce faire, nous pouvons utiliser le sync.Poolpackage dans Go pour implémenter le regroupement d'objets. Il sync.Pools'agit d'un package Go intégré qui fournit un mécanisme thread-safe pour stocker et récupérer des objets à partir d'un pool.
Peut sauter directement dans le code .. !!

Gestionnaires : -


package handlers

import (
 "encoding/json"
 "net/http"
 "sync"
)

type Request struct {
 Name    string `json:"name"`
 Id      string `json:"id"`
 Country string `json:"country"`
 Age     int    `json:"age"`
}

type ObjectPool struct {
 ReqPool *sync.Pool
}

var objPool ObjectPool

func init() {
 // init object pool
 objPool.ReqPool = &sync.Pool{
  New: func() interface{} {
   return new(Request)
  },
 }

}


func HandlerWithoutSyncPool() http.Handler {
 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  req :=new(Request)
  err := json.NewDecoder(r.Body).Decode(&req)
  if err != nil {
   w.WriteHeader(http.StatusBadRequest)
   return
  }
  w.WriteHeader(http.StatusOK)
  err = json.NewEncoder(w).Encode(req)
  if err != nil {
   w.WriteHeader(http.StatusInternalServerError)
   return
  }

 })
}

func HandlerWithSyncPool() http.Handler {
 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  req := objPool.ReqPool.Get().(*Request)

 //to make sure it return object to pool
  defer objPool.ReqPool.Put(req)

  err := json.NewDecoder(r.Body).Decode(&req)
  if err != nil {
   w.WriteHeader(http.StatusBadRequest)
   return
  }

  w.WriteHeader(http.StatusOK)
  
 })
}

Remarque : les résultats varient selon le type d'objet que nous regroupons

package handlers

import (
 "bytes"
 "encoding/json"
 "net/http"
 "net/http/httptest"
 "testing"
)

var tests = []struct {
 req Request
}{
 {
  req: Request{Name: "test", Country: "testCountry", Id: "id1", Age: 10},
 },
}


func BenchmarkHandlerWithSyncPool(b *testing.B) {

 for _, test := range tests {
  data, _ := json.Marshal(test)

  b.RunParallel(func(pb *testing.PB) {

   for pb.Next() {
    _, _ = http.Post("http://127.0.0.1:8888/withpool", "application/json", bytes.NewBuffer(data))
   }
  })

 }

}

func BenchmarkHandlerWithoutSyncPool(b *testing.B) {
 for _, test := range tests {

  data, _ := json.Marshal(test)

  b.RunParallel(func(pb *testing.PB) {

   for pb.Next() {
    _, _ = http.Post("http://127.0.0.1:8888/withoutpool", "application/json", bytes.NewBuffer(data))
   }
  })
 }

}


go test -bench=BenchmarkHandlerWithoutSyncPool -benchtime=5s -cpu=1
goos: darwin
goarch: amd64
pkg: testsyncpool/handlers
cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
BenchmarkHandlerWithoutSyncPool     23067    1004690 ns/op

      
                

go test -bench=BenchmarkHandlerWithSyncPool -benchtime=5s -cpu=1 
goos: darwin
goarch: amd64
pkg: testsyncpool/handlers
cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
BenchmarkHandlerWithSyncPool     77290      76521 ns/op

      
                

package main

import (
 "log"
 "net/http"
 "net/http/pprof"
 "testsyncpool/handlers"
)

func main() {
 r := http.NewServeMux()
 //r.Handle("/withoutpool", handlers.HandlerWithoutSyncPool())

 r.Handle("/withpool", handlers.HandlerWithSyncPool())

 // Register pprof handlers
 r.HandleFunc("/debug/pprof/", pprof.Index)
 r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
 r.HandleFunc("/debug/pprof/profile", pprof.Profile)
 r.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
 r.HandleFunc("/debug/pprof/trace", pprof.Trace)
 err := http.ListenAndServe(":8888", r)
 if err != nil {
  log.Fatal(err)
 }
}

N'hésitez pas à ajouter des commentaires/suggestions/erreurs etc…