API を高速化する

Feb 21 2023
新しいオブジェクトの作成は、特にヒープ上にメモリを割り当てる必要がある場合、比較的高価な操作となる可能性があるため、オブジェクトを再利用するとオーバーヘッドが削減され、パフォーマンスが向上します。preAllocated オブジェクトのプールを保持し、必要に応じて再利用することで、メモリの割り当てと割り当て解除のオーバーヘッドを回避できます (ガベージ コレクターはメモリ内の未使用の変数の割り当てを解除します)。

新しいオブジェクトの作成は、特にヒープ上にメモリを割り当てる必要がある場合、比較的高価な操作となる可能性があるため、オブジェクトを再利用するとオーバーヘッドが削減され、パフォーマンスが向上します。preAllocated オブジェクトのプールを保持し、必要に応じて再利用することで、メモリの割り当てと割り当て解除のオーバーヘッドを回避できます (ガベージ コレクターはメモリ内の未使用の変数の割り当てを解除します)。

これは、リクエストごとにオブジェクトを作成および破棄することが重大なボトルネックになる可能性がある、多数のリクエストを処理する必要がある API で特に役立ちます。オブジェクトを再利用することで、必要な CPU 時間とメモリの量を削減でき、パフォーマンスが向上し、より多くのリクエストを処理できるようになります。

これを実現するには、sync.PoolGo でパッケージを使用してオブジェクト プーリングを実装します。これは、sync.Poolプールにオブジェクトを格納および取得するためのスレッドセーフなメカニズムを提供する組み込みの Go パッケージです。
コードに直接ジャンプできます..!!

ハンドラー:-


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)
  
 })
}

注: - 結果は、プールしているオブジェクトのタイプによって異なります。

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)
 }
}

コメント/提案/間違いなどお気軽に追加してください…