Skip to content

Commit

Permalink
net/textproto: simplify common header interning
Browse files Browse the repository at this point in the history
Takes advantage of CL 83740044, to optimize map[string] lookup
from []byte key.

Deletes code.

No conditional check for gccgo, since Ian plans to add this
to gccgo before GCC 4.10 (Go 1.3).

benchmark                   old ns/op     new ns/op     delta
BenchmarkReadMIMEHeader     6066          5086          -16.16%

benchmark                   old allocs     new allocs     delta
BenchmarkReadMIMEHeader     12             12             +0.00%

benchmark                   old bytes     new bytes     delta
BenchmarkReadMIMEHeader     1317          1317          +0.00%

Update #3512

LGTM=rsc
R=rsc, dave
CC=golang-codereviews, iant
https://golang.org/cl/84230043
  • Loading branch information
bradfitz committed Apr 7, 2014
1 parent 24192bb commit 8072f46
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 75 deletions.
112 changes: 53 additions & 59 deletions src/pkg/net/textproto/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -562,19 +562,12 @@ const toLower = 'a' - 'A'
// allowed to mutate the provided byte slice before returning the
// string.
func canonicalMIMEHeaderKey(a []byte) string {
// Look for it in commonHeaders , so that we can avoid an
// allocation by sharing the strings among all users
// of textproto. If we don't find it, a has been canonicalized
// so just return string(a).
upper := true
lo := 0
hi := len(commonHeaders)
for i := 0; i < len(a); i++ {
for i, c := range a {
// Canonicalize: first letter upper case
// and upper case after each dash.
// (Host, User-Agent, If-Modified-Since).
// MIME headers are ASCII only, so no Unicode issues.
c := a[i]
if c == ' ' {
c = '-'
} else if upper && 'a' <= c && c <= 'z' {
Expand All @@ -584,60 +577,61 @@ func canonicalMIMEHeaderKey(a []byte) string {
}
a[i] = c
upper = c == '-' // for next time

if lo < hi {
for lo < hi && (len(commonHeaders[lo]) <= i || commonHeaders[lo][i] < c) {
lo++
}
for hi > lo && commonHeaders[hi-1][i] > c {
hi--
}
}
}
if lo < hi && len(commonHeaders[lo]) == len(a) {
return commonHeaders[lo]
// The compiler recognizes m[string(byteSlice)] as a special
// case, so a copy of a's bytes into a new string does not
// happen in this map lookup:
if v := commonHeader[string(a)]; v != "" {
return v
}
return string(a)
}

var commonHeaders = []string{
"Accept",
"Accept-Charset",
"Accept-Encoding",
"Accept-Language",
"Accept-Ranges",
"Cache-Control",
"Cc",
"Connection",
"Content-Id",
"Content-Language",
"Content-Length",
"Content-Transfer-Encoding",
"Content-Type",
"Cookie",
"Date",
"Dkim-Signature",
"Etag",
"Expires",
"From",
"Host",
"If-Modified-Since",
"If-None-Match",
"In-Reply-To",
"Last-Modified",
"Location",
"Message-Id",
"Mime-Version",
"Pragma",
"Received",
"Return-Path",
"Server",
"Set-Cookie",
"Subject",
"To",
"User-Agent",
"Via",
"X-Forwarded-For",
"X-Imforwards",
"X-Powered-By",
// commonHeader interns common header strings.
var commonHeader = make(map[string]string)

func init() {
for _, v := range []string{
"Accept",
"Accept-Charset",
"Accept-Encoding",
"Accept-Language",
"Accept-Ranges",
"Cache-Control",
"Cc",
"Connection",
"Content-Id",
"Content-Language",
"Content-Length",
"Content-Transfer-Encoding",
"Content-Type",
"Cookie",
"Date",
"Dkim-Signature",
"Etag",
"Expires",
"From",
"Host",
"If-Modified-Since",
"If-None-Match",
"In-Reply-To",
"Last-Modified",
"Location",
"Message-Id",
"Mime-Version",
"Pragma",
"Received",
"Return-Path",
"Server",
"Set-Cookie",
"Subject",
"To",
"User-Agent",
"Via",
"X-Forwarded-For",
"X-Imforwards",
"X-Powered-By",
} {
commonHeader[v] = v
}
}
28 changes: 12 additions & 16 deletions src/pkg/net/textproto/reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,24 +247,20 @@ func TestRFC959Lines(t *testing.T) {
}

func TestCommonHeaders(t *testing.T) {
// need to disable the commonHeaders-based optimization
// during this check, or we'd not be testing anything
oldch := commonHeaders
commonHeaders = []string{}
defer func() { commonHeaders = oldch }()

last := ""
for _, h := range oldch {
if last > h {
t.Errorf("%v is out of order", h)
}
if last == h {
t.Errorf("%v is duplicated", h)
for h := range commonHeader {
if h != CanonicalMIMEHeaderKey(h) {
t.Errorf("Non-canonical header %q in commonHeader", h)
}
if canon := CanonicalMIMEHeaderKey(h); h != canon {
t.Errorf("%v is not canonical", h)
}
b := []byte("content-Length")
want := "Content-Length"
n := testing.AllocsPerRun(200, func() {
if x := canonicalMIMEHeaderKey(b); x != want {
t.Fatalf("canonicalMIMEHeaderKey(%q) = %q; want %q", b, x, want)
}
last = h
})
if n > 0 {
t.Errorf("canonicalMIMEHeaderKey allocs = %v; want 0", n)
}
}

Expand Down

0 comments on commit 8072f46

Please sign in to comment.