Commit a30bede5 authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

net/http: remove allocations in HeaderWriteSubset

Before:
BenchmarkHeaderWriteSubset  500000  2354 ns/op  197 B/op  2 allocs/op
After:
BenchmarkHeaderWriteSubset 1000000  2085 ns/op    0 B/op  0 allocs/op

Fixes #3761

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7508043
parent fb59aed6
...@@ -103,21 +103,41 @@ type keyValues struct { ...@@ -103,21 +103,41 @@ type keyValues struct {
values []string values []string
} }
type byKey []keyValues // A headerSorter implements sort.Interface by sorting a []keyValues
// by key. It's used as a pointer, so it can fit in a sort.Interface
func (s byKey) Len() int { return len(s) } // interface value without allocation.
func (s byKey) Swap(i, j int) { s[i], s[j] = s[j], s[i] } type headerSorter struct {
func (s byKey) Less(i, j int) bool { return s[i].key < s[j].key } kvs []keyValues
}
func (h Header) sortedKeyValues(exclude map[string]bool) []keyValues {
kvs := make([]keyValues, 0, len(h)) func (s *headerSorter) Len() int { return len(s.kvs) }
func (s *headerSorter) Swap(i, j int) { s.kvs[i], s.kvs[j] = s.kvs[j], s.kvs[i] }
func (s *headerSorter) Less(i, j int) bool { return s.kvs[i].key < s.kvs[j].key }
// TODO: convert this to a sync.Cache (issue 4720)
var headerSorterCache = make(chan *headerSorter, 8)
// sortedKeyValues returns h's keys sorted in the returned kvs
// slice. The headerSorter used to sort is also returned, for possible
// return to headerSorterCache.
func (h Header) sortedKeyValues(exclude map[string]bool) (kvs []keyValues, hs *headerSorter) {
select {
case hs = <-headerSorterCache:
default:
hs = new(headerSorter)
}
if cap(hs.kvs) < len(h) {
hs.kvs = make([]keyValues, 0, len(h))
}
kvs = hs.kvs[:0]
for k, vv := range h { for k, vv := range h {
if !exclude[k] { if !exclude[k] {
kvs = append(kvs, keyValues{k, vv}) kvs = append(kvs, keyValues{k, vv})
} }
} }
sort.Sort(byKey(kvs)) hs.kvs = kvs
return kvs sort.Sort(hs)
return kvs, hs
} }
// WriteSubset writes a header in wire format. // WriteSubset writes a header in wire format.
...@@ -127,7 +147,8 @@ func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error { ...@@ -127,7 +147,8 @@ func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error {
if !ok { if !ok {
ws = stringWriter{w} ws = stringWriter{w}
} }
for _, kv := range h.sortedKeyValues(exclude) { kvs, sorter := h.sortedKeyValues(exclude)
for _, kv := range kvs {
for _, v := range kv.values { for _, v := range kv.values {
v = headerNewlineToSpace.Replace(v) v = headerNewlineToSpace.Replace(v)
v = textproto.TrimString(v) v = textproto.TrimString(v)
...@@ -138,6 +159,10 @@ func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error { ...@@ -138,6 +159,10 @@ func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error {
} }
} }
} }
select {
case headerSorterCache <- sorter:
default:
}
return nil return nil
} }
......
...@@ -196,9 +196,7 @@ func TestHeaderWriteSubsetMallocs(t *testing.T) { ...@@ -196,9 +196,7 @@ func TestHeaderWriteSubsetMallocs(t *testing.T) {
buf.Reset() buf.Reset()
testHeader.WriteSubset(&buf, nil) testHeader.WriteSubset(&buf, nil)
}) })
if n > 1 { if n > 0 {
// TODO(bradfitz,rsc): once we can sort without allocating, t.Errorf("mallocs = %d; want 0", n)
// make this an error. See http://golang.org/issue/3761
// t.Errorf("got %v allocs, want <= %v", n, 1)
} }
} }
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