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 {
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
// interface value without allocation.
type headerSorter struct {
kvs []keyValues
}
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 }
func (s byKey) Len() int { return len(s) }
func (s byKey) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s byKey) Less(i, j int) bool { return s[i].key < s[j].key }
// TODO: convert this to a sync.Cache (issue 4720)
var headerSorterCache = make(chan *headerSorter, 8)
func (h Header) sortedKeyValues(exclude map[string]bool) []keyValues {
kvs := make([]keyValues, 0, len(h))
// 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 {
if !exclude[k] {
kvs = append(kvs, keyValues{k, vv})
}
}
sort.Sort(byKey(kvs))
return kvs
hs.kvs = kvs
sort.Sort(hs)
return kvs, hs
}
// WriteSubset writes a header in wire format.
......@@ -127,7 +147,8 @@ func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error {
if !ok {
ws = stringWriter{w}
}
for _, kv := range h.sortedKeyValues(exclude) {
kvs, sorter := h.sortedKeyValues(exclude)
for _, kv := range kvs {
for _, v := range kv.values {
v = headerNewlineToSpace.Replace(v)
v = textproto.TrimString(v)
......@@ -138,6 +159,10 @@ func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error {
}
}
}
select {
case headerSorterCache <- sorter:
default:
}
return nil
}
......
......@@ -196,9 +196,7 @@ func TestHeaderWriteSubsetMallocs(t *testing.T) {
buf.Reset()
testHeader.WriteSubset(&buf, nil)
})
if n > 1 {
// TODO(bradfitz,rsc): once we can sort without allocating,
// make this an error. See http://golang.org/issue/3761
// t.Errorf("got %v allocs, want <= %v", n, 1)
if n > 0 {
t.Errorf("mallocs = %d; want 0", n)
}
}
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