mirror of https://github.com/golang/go.git
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
This commit is contained in:
parent
fb59aed60b
commit
a30bede5ef
|
|
@ -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
|
||||||
|
// interface value without allocation.
|
||||||
|
type headerSorter struct {
|
||||||
|
kvs []keyValues
|
||||||
|
}
|
||||||
|
|
||||||
func (s byKey) Len() int { return len(s) }
|
func (s *headerSorter) Len() int { return len(s.kvs) }
|
||||||
func (s byKey) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
func (s *headerSorter) Swap(i, j int) { s.kvs[i], s.kvs[j] = s.kvs[j], s.kvs[i] }
|
||||||
func (s byKey) Less(i, j int) bool { return s[i].key < s[j].key }
|
func (s *headerSorter) Less(i, j int) bool { return s.kvs[i].key < s.kvs[j].key }
|
||||||
|
|
||||||
func (h Header) sortedKeyValues(exclude map[string]bool) []keyValues {
|
// TODO: convert this to a sync.Cache (issue 4720)
|
||||||
kvs := make([]keyValues, 0, len(h))
|
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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
select {
|
||||||
|
case headerSorterCache <- sorter:
|
||||||
|
default:
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue