mirror of
https://github.com/golang/go
synced 2024-11-22 21:40:03 -07:00
net/http: fewer allocations in the server path
Don't allocate for the Date or Content-Length headers. A custom Date header formatter replaces use of time.Format. benchmark old ns/op new ns/op delta BenchmarkClientServer 67791 64424 -4.97% BenchmarkClientServerParallel4 62956 58533 -7.03% BenchmarkClientServerParallel64 62043 54789 -11.69% BenchmarkServer 254609 229060 -10.03% BenchmarkServerFakeConnNoKeepAlive 17038 16316 -4.24% BenchmarkServerFakeConnWithKeepAlive 14184 13226 -6.75% BenchmarkServerFakeConnWithKeepAliveLite 8591 7532 -12.33% BenchmarkServerHandlerTypeLen 10750 9961 -7.34% BenchmarkServerHandlerNoLen 9535 8935 -6.29% BenchmarkServerHandlerNoType 9858 9362 -5.03% BenchmarkServerHandlerNoHeader 7754 6856 -11.58% benchmark old allocs new allocs delta BenchmarkClientServer 68 66 -2.94% BenchmarkClientServerParallel4 68 66 -2.94% BenchmarkClientServerParallel64 68 66 -2.94% BenchmarkServer 21 19 -9.52% BenchmarkServerFakeConnNoKeepAlive 32 30 -6.25% BenchmarkServerFakeConnWithKeepAlive 27 25 -7.41% BenchmarkServerFakeConnWithKeepAliveLite 12 10 -16.67% BenchmarkServerHandlerTypeLen 19 18 -5.26% BenchmarkServerHandlerNoLen 17 15 -11.76% BenchmarkServerHandlerNoType 17 16 -5.88% BenchmarkServerHandlerNoHeader 12 10 -16.67% Update #5195 R=nigeltao CC=golang-dev https://golang.org/cl/9432046
This commit is contained in:
parent
38abb09a2e
commit
d4cbc80d10
@ -1727,6 +1727,7 @@ func TestAcceptMaxFds(t *testing.T) {
|
||||
}
|
||||
|
||||
func BenchmarkClientServer(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.StopTimer()
|
||||
ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, r *Request) {
|
||||
fmt.Fprintf(rw, "Hello world.\n")
|
||||
@ -1761,6 +1762,7 @@ func BenchmarkClientServerParallel64(b *testing.B) {
|
||||
}
|
||||
|
||||
func benchmarkClientServerParallel(b *testing.B, conc int) {
|
||||
b.ReportAllocs()
|
||||
b.StopTimer()
|
||||
ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, r *Request) {
|
||||
fmt.Fprintf(rw, "Hello world.\n")
|
||||
@ -1805,6 +1807,7 @@ func benchmarkClientServerParallel(b *testing.B, conc int) {
|
||||
// $ go tool pprof http.test http.prof
|
||||
// (pprof) web
|
||||
func BenchmarkServer(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
// Child process mode;
|
||||
if url := os.Getenv("TEST_BENCH_SERVER_URL"); url != "" {
|
||||
n, err := strconv.Atoi(os.Getenv("TEST_BENCH_CLIENT_N"))
|
||||
|
@ -320,6 +320,10 @@ type response struct {
|
||||
requestBodyLimitHit bool
|
||||
|
||||
handlerDone bool // set true when the handler exits
|
||||
|
||||
// Buffers for Date and Content-Length
|
||||
dateBuf [len(TimeFormat)]byte
|
||||
clenBuf [10]byte
|
||||
}
|
||||
|
||||
// requestTooLarge is called by maxBytesReader when too much input has
|
||||
@ -525,6 +529,27 @@ func (ecr *expectContinueReader) Close() error {
|
||||
// It is like time.RFC1123 but hard codes GMT as the time zone.
|
||||
const TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
|
||||
|
||||
// appendTime is a non-allocating version of []byte(time.Now().UTC().Format(TimeFormat))
|
||||
func appendTime(b []byte, t time.Time) []byte {
|
||||
const days = "SunMonTueWedThuFriSat"
|
||||
const months = "JanFebMarAprMayJunJulAugSepOctNovDec"
|
||||
|
||||
yy, mm, dd := t.Date()
|
||||
hh, mn, ss := t.Clock()
|
||||
day := days[3*t.Weekday():]
|
||||
mon := months[3*(mm-1):]
|
||||
|
||||
return append(b,
|
||||
day[0], day[1], day[2], ',', ' ',
|
||||
byte('0'+dd/10), byte('0'+dd%10), ' ',
|
||||
mon[0], mon[1], mon[2], ' ',
|
||||
byte('0'+yy/1000), byte('0'+(yy/100)%10), byte('0'+(yy/10)%10), byte('0'+yy%10), ' ',
|
||||
byte('0'+hh/10), byte('0'+hh%10), ':',
|
||||
byte('0'+mn/10), byte('0'+mn%10), ':',
|
||||
byte('0'+ss/10), byte('0'+ss%10), ' ',
|
||||
'G', 'M', 'T')
|
||||
}
|
||||
|
||||
var errTooLarge = errors.New("http: request too large")
|
||||
|
||||
// Read next request from connection.
|
||||
@ -620,27 +645,45 @@ func (w *response) WriteHeader(code int) {
|
||||
// the response Header map and all its 1-element slices.
|
||||
type extraHeader struct {
|
||||
contentType string
|
||||
contentLength string
|
||||
connection string
|
||||
date string
|
||||
transferEncoding string
|
||||
date []byte // written if not nil
|
||||
contentLength []byte // written if not nil
|
||||
}
|
||||
|
||||
// Sorted the same as extraHeader.Write's loop.
|
||||
var extraHeaderKeys = [][]byte{
|
||||
[]byte("Content-Type"), []byte("Content-Length"),
|
||||
[]byte("Connection"), []byte("Date"), []byte("Transfer-Encoding"),
|
||||
[]byte("Content-Type"),
|
||||
[]byte("Connection"),
|
||||
[]byte("Transfer-Encoding"),
|
||||
}
|
||||
|
||||
// The value receiver, despite copying 5 strings to the stack,
|
||||
// prevents an extra allocation. The escape analysis isn't smart
|
||||
// enough to realize this doesn't mutate h.
|
||||
func (h extraHeader) Write(w io.Writer) {
|
||||
for i, v := range []string{h.contentType, h.contentLength, h.connection, h.date, h.transferEncoding} {
|
||||
var (
|
||||
headerContentLength = []byte("Content-Length: ")
|
||||
headerDate = []byte("Date: ")
|
||||
)
|
||||
|
||||
// Write writes the headers described in h to w.
|
||||
//
|
||||
// This method has a value receiver, despite the somewhat large size
|
||||
// of h, because it prevents an allocation. The escape analysis isn't
|
||||
// smart enough to realize this function doesn't mutate h.
|
||||
func (h extraHeader) Write(w *bufio.Writer) {
|
||||
if h.date != nil {
|
||||
w.Write(headerDate)
|
||||
w.Write(h.date)
|
||||
w.Write(crlf)
|
||||
}
|
||||
if h.contentLength != nil {
|
||||
w.Write(headerContentLength)
|
||||
w.Write(h.contentLength)
|
||||
w.Write(crlf)
|
||||
}
|
||||
for i, v := range []string{h.contentType, h.connection, h.transferEncoding} {
|
||||
if v != "" {
|
||||
w.Write(extraHeaderKeys[i])
|
||||
w.Write(colonSpace)
|
||||
io.WriteString(w, v)
|
||||
w.WriteString(v)
|
||||
w.Write(crlf)
|
||||
}
|
||||
}
|
||||
@ -694,7 +737,7 @@ func (cw *chunkWriter) writeHeader(p []byte) {
|
||||
// "keep-alive" connections alive.
|
||||
if w.handlerDone && header.get("Content-Length") == "" && w.req.Method != "HEAD" {
|
||||
w.contentLength = int64(len(p))
|
||||
setHeader.contentLength = strconv.Itoa(len(p))
|
||||
setHeader.contentLength = strconv.AppendInt(cw.res.clenBuf[:0], int64(len(p)), 10)
|
||||
}
|
||||
|
||||
// If this was an HTTP/1.0 request with keep-alive and we sent a
|
||||
@ -755,7 +798,7 @@ func (cw *chunkWriter) writeHeader(p []byte) {
|
||||
}
|
||||
|
||||
if _, ok := header["Date"]; !ok {
|
||||
setHeader.date = time.Now().UTC().Format(TimeFormat)
|
||||
setHeader.date = appendTime(cw.res.dateBuf[:0], time.Now())
|
||||
}
|
||||
|
||||
te := header.get("Transfer-Encoding")
|
||||
@ -806,7 +849,7 @@ func (cw *chunkWriter) writeHeader(p []byte) {
|
||||
|
||||
io.WriteString(w.conn.buf, statusLine(w.req, code))
|
||||
cw.header.WriteSubset(w.conn.buf, excludeHeader)
|
||||
setHeader.Write(w.conn.buf)
|
||||
setHeader.Write(w.conn.buf.Writer)
|
||||
w.conn.buf.Write(crlf)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user