Commit 4876518f authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

http2: don't make garbage when sorting things

benchmark                        old ns/op     new ns/op     delta
BenchmarkServer_GetRequest-2     259453        256050        -1.31%

benchmark                        old allocs     new allocs     delta
BenchmarkServer_GetRequest-2     24             22             -8.33%

benchmark                        old bytes     new bytes     delta
BenchmarkServer_GetRequest-2     1599          1532          -4.19%

Change-Id: Ieb11a3bd4c752567e0401bcc5e77e027cfb8063c
Reviewed-on: https://go-review.googlesource.com/20999Reviewed-by: 's avatarAndrew Gerrand <adg@golang.org>
parent c5617807
...@@ -23,6 +23,7 @@ import ( ...@@ -23,6 +23,7 @@ import (
"io" "io"
"net/http" "net/http"
"os" "os"
"sort"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
...@@ -427,3 +428,36 @@ var isTokenTable = [127]bool{ ...@@ -427,3 +428,36 @@ var isTokenTable = [127]bool{
type connectionStater interface { type connectionStater interface {
ConnectionState() tls.ConnectionState ConnectionState() tls.ConnectionState
} }
var sorterPool = sync.Pool{New: func() interface{} { return new(sorter) }}
type sorter struct {
v []string // owned by sorter
}
func (s *sorter) Len() int { return len(s.v) }
func (s *sorter) Swap(i, j int) { s.v[i], s.v[j] = s.v[j], s.v[i] }
func (s *sorter) Less(i, j int) bool { return s.v[i] < s.v[j] }
// Keys returns the sorted keys of h.
//
// The returned slice is only valid until s used again or returned to
// its pool.
func (s *sorter) Keys(h http.Header) []string {
keys := s.v[:0]
for k := range h {
keys = append(keys, k)
}
s.v = keys
sort.Sort(s)
return keys
}
func (s *sorter) SortStrings(ss []string) {
// Our sorter works on s.v, which sorter owners, so
// stash it away while we sort the user's buffer.
save := s.v
s.v = ss
sort.Sort(s)
s.v = save
}
...@@ -172,3 +172,27 @@ func cleanDate(res *http.Response) { ...@@ -172,3 +172,27 @@ func cleanDate(res *http.Response) {
d[0] = "XXX" d[0] = "XXX"
} }
} }
func TestSorterPoolAllocs(t *testing.T) {
ss := []string{"a", "b", "c"}
h := http.Header{
"a": nil,
"b": nil,
"c": nil,
}
sorter := new(sorter)
if allocs := testing.AllocsPerRun(100, func() {
sorter.SortStrings(ss)
}); allocs >= 1 {
t.Logf("SortStrings allocs = %v; want <1", allocs)
}
if allocs := testing.AllocsPerRun(5, func() {
if len(sorter.Keys(h)) != 3 {
t.Fatal("wrong result")
}
}); allocs > 0 {
t.Logf("Keys allocs = %v; want <1", allocs)
}
}
...@@ -51,7 +51,6 @@ import ( ...@@ -51,7 +51,6 @@ import (
"os" "os"
"reflect" "reflect"
"runtime" "runtime"
"sort"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
...@@ -2026,7 +2025,12 @@ func (rws *responseWriterState) promoteUndeclaredTrailers() { ...@@ -2026,7 +2025,12 @@ func (rws *responseWriterState) promoteUndeclaredTrailers() {
rws.declareTrailer(trailerKey) rws.declareTrailer(trailerKey)
rws.handlerHeader[http.CanonicalHeaderKey(trailerKey)] = vv rws.handlerHeader[http.CanonicalHeaderKey(trailerKey)] = vv
} }
sort.Strings(rws.trailers)
if len(rws.trailers) > 1 {
sorter := sorterPool.Get().(*sorter)
sorter.SortStrings(rws.trailers)
sorterPool.Put(sorter)
}
} }
func (w *responseWriter) Flush() { func (w *responseWriter) Flush() {
......
...@@ -9,7 +9,6 @@ import ( ...@@ -9,7 +9,6 @@ import (
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
"sort"
"time" "time"
"golang.org/x/net/http2/hpack" "golang.org/x/net/http2/hpack"
...@@ -230,13 +229,13 @@ func (wu writeWindowUpdate) writeFrame(ctx writeContext) error { ...@@ -230,13 +229,13 @@ func (wu writeWindowUpdate) writeFrame(ctx writeContext) error {
} }
func encodeHeaders(enc *hpack.Encoder, h http.Header, keys []string) { func encodeHeaders(enc *hpack.Encoder, h http.Header, keys []string) {
// TODO: garbage. pool sorters like http1? hot path for 1 key?
if keys == nil { if keys == nil {
keys = make([]string, 0, len(h)) sorter := sorterPool.Get().(*sorter)
for k := range h { // Using defer here, since the returned keys from the
keys = append(keys, k) // sorter.Keys method is only valid until the sorter
} // is returned:
sort.Strings(keys) defer sorterPool.Put(sorter)
keys = sorter.Keys(h)
} }
for _, k := range keys { for _, k := range keys {
vv := h[k] vv := h[k]
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment