1
0
mirror of https://github.com/golang/go synced 2024-11-26 02:57:57 -07:00

net/textproto: reduce allocations in ReadMIMEHeader

ReadMIMEHeader is used by net/http, net/mail, and
mime/multipart.

Don't do so many small allocations. Calculate up front
how much we'll probably need.

benchmark                  old ns/op    new ns/op    delta
BenchmarkReadMIMEHeader         8433         7467  -11.45%

benchmark                 old allocs   new allocs    delta
BenchmarkReadMIMEHeader           23           14  -39.13%

benchmark                  old bytes    new bytes    delta
BenchmarkReadMIMEHeader         1705         1343  -21.23%

R=golang-dev, r, iant, adg
CC=golang-dev
https://golang.org/cl/8179043
This commit is contained in:
Brad Fitzpatrick 2013-07-02 22:37:19 -07:00
parent b4d4ff9381
commit 16c3f82ed4

View File

@ -456,7 +456,16 @@ func (r *Reader) ReadDotLines() ([]string, error) {
// } // }
// //
func (r *Reader) ReadMIMEHeader() (MIMEHeader, error) { func (r *Reader) ReadMIMEHeader() (MIMEHeader, error) {
m := make(MIMEHeader, 4) // Avoid lots of small slice allocations later by allocating one
// large one ahead of time which we'll cut up into smaller
// slices. If this isn't big enough later, we allocate small ones.
var strs []string
hint := r.upcomingHeaderNewlines()
if hint > 0 {
strs = make([]string, hint)
}
m := make(MIMEHeader, hint)
for { for {
kv, err := r.readContinuedLineSlice() kv, err := r.readContinuedLineSlice()
if len(kv) == 0 { if len(kv) == 0 {
@ -483,7 +492,18 @@ func (r *Reader) ReadMIMEHeader() (MIMEHeader, error) {
} }
value := string(kv[i:]) value := string(kv[i:])
m[key] = append(m[key], value) vv := m[key]
if vv == nil && len(strs) > 0 {
// More than likely this will be a single-element key.
// Most headers aren't multi-valued.
// Set the capacity on strs[0] to 1, so any future append
// won't extend the slice into the other strings.
vv, strs = strs[:1:1], strs[1:]
vv[0] = value
m[key] = vv
} else {
m[key] = append(vv, value)
}
if err != nil { if err != nil {
return m, err return m, err
@ -491,6 +511,29 @@ func (r *Reader) ReadMIMEHeader() (MIMEHeader, error) {
} }
} }
// upcomingHeaderNewlines returns an approximation of the number of newlines
// that will be in this header. If it gets confused, it returns 0.
func (r *Reader) upcomingHeaderNewlines() (n int) {
// Try to determine the 'hint' size.
r.R.Peek(1) // force a buffer load if empty
s := r.R.Buffered()
if s == 0 {
return
}
peek, _ := r.R.Peek(s)
for len(peek) > 0 {
i := bytes.IndexByte(peek, '\n')
if i < 3 {
// Not present (-1) or found within the next few bytes,
// implying we're at the end ("\r\n\r\n" or "\n\n")
return
}
n++
peek = peek[i+1:]
}
return
}
// CanonicalMIMEHeaderKey returns the canonical format of the // CanonicalMIMEHeaderKey returns the canonical format of the
// MIME header key s. The canonicalization converts the first // MIME header key s. The canonicalization converts the first
// letter and any letter following a hyphen to upper case; // letter and any letter following a hyphen to upper case;