tsweb: memoize the string forms of HTTP response codes.
Saves 1-2 allocs per HTTP request after warmup, thanks to
avoiding strconv and fmt.
"runtime"
"strconv"
"strings"
+ "sync"
"time"
"go4.org/mem"
@@ -302,15 +303,38 @@ func (h retHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
if h.opts.StatusCodeCounters != nil {
- key := fmt.Sprintf("%dxx", msg.Code/100)
- h.opts.StatusCodeCounters.Add(key, 1)
+ h.opts.StatusCodeCounters.Add(responseCodeString(msg.Code/100), 1)
}
if h.opts.StatusCodeCountersFull != nil {
- h.opts.StatusCodeCountersFull.Add(strconv.Itoa(msg.Code), 1)
+ h.opts.StatusCodeCountersFull.Add(responseCodeString(msg.Code), 1)
}
}
+func responseCodeString(code int) string {
+ if v, ok := responseCodeCache.Load(code); ok {
+ return v.(string)
+ }
+
+ var ret string
+ if code < 10 {
+ ret = fmt.Sprintf("%dxx", code)
+ } else {
+ ret = strconv.Itoa(code)
+ }
+ responseCodeCache.Store(code, ret)
+ return ret
+}
+
+// responseCodeCache memoizes the string form of HTTP response codes,
+// so that the hot request-handling codepath doesn't have to allocate
+// in strconv/fmt for every request.
+//
+// Keys are either full HTTP response code ints (200, 404) or "family"
+// ints representing entire families (e.g. 2 for 2xx codes). Values
+// are the string form of that code/family.
+var responseCodeCache sync.Map
+
// loggingResponseWriter wraps a ResponseWriter and record the HTTP
// response code that gets sent, if any.
type loggingResponseWriter struct {
