mirror of
https://github.com/golang/go
synced 2024-11-22 22:20: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) {
|
func BenchmarkClientServer(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, r *Request) {
|
ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, r *Request) {
|
||||||
fmt.Fprintf(rw, "Hello world.\n")
|
fmt.Fprintf(rw, "Hello world.\n")
|
||||||
@ -1761,6 +1762,7 @@ func BenchmarkClientServerParallel64(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func benchmarkClientServerParallel(b *testing.B, conc int) {
|
func benchmarkClientServerParallel(b *testing.B, conc int) {
|
||||||
|
b.ReportAllocs()
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, r *Request) {
|
ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, r *Request) {
|
||||||
fmt.Fprintf(rw, "Hello world.\n")
|
fmt.Fprintf(rw, "Hello world.\n")
|
||||||
@ -1805,6 +1807,7 @@ func benchmarkClientServerParallel(b *testing.B, conc int) {
|
|||||||
// $ go tool pprof http.test http.prof
|
// $ go tool pprof http.test http.prof
|
||||||
// (pprof) web
|
// (pprof) web
|
||||||
func BenchmarkServer(b *testing.B) {
|
func BenchmarkServer(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
// Child process mode;
|
// Child process mode;
|
||||||
if url := os.Getenv("TEST_BENCH_SERVER_URL"); url != "" {
|
if url := os.Getenv("TEST_BENCH_SERVER_URL"); url != "" {
|
||||||
n, err := strconv.Atoi(os.Getenv("TEST_BENCH_CLIENT_N"))
|
n, err := strconv.Atoi(os.Getenv("TEST_BENCH_CLIENT_N"))
|
||||||
|
@ -320,6 +320,10 @@ type response struct {
|
|||||||
requestBodyLimitHit bool
|
requestBodyLimitHit bool
|
||||||
|
|
||||||
handlerDone bool // set true when the handler exits
|
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
|
// 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.
|
// It is like time.RFC1123 but hard codes GMT as the time zone.
|
||||||
const TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
|
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")
|
var errTooLarge = errors.New("http: request too large")
|
||||||
|
|
||||||
// Read next request from connection.
|
// 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.
|
// the response Header map and all its 1-element slices.
|
||||||
type extraHeader struct {
|
type extraHeader struct {
|
||||||
contentType string
|
contentType string
|
||||||
contentLength string
|
|
||||||
connection string
|
connection string
|
||||||
date string
|
|
||||||
transferEncoding string
|
transferEncoding string
|
||||||
|
date []byte // written if not nil
|
||||||
|
contentLength []byte // written if not nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sorted the same as extraHeader.Write's loop.
|
// Sorted the same as extraHeader.Write's loop.
|
||||||
var extraHeaderKeys = [][]byte{
|
var extraHeaderKeys = [][]byte{
|
||||||
[]byte("Content-Type"), []byte("Content-Length"),
|
[]byte("Content-Type"),
|
||||||
[]byte("Connection"), []byte("Date"), []byte("Transfer-Encoding"),
|
[]byte("Connection"),
|
||||||
|
[]byte("Transfer-Encoding"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// The value receiver, despite copying 5 strings to the stack,
|
var (
|
||||||
// prevents an extra allocation. The escape analysis isn't smart
|
headerContentLength = []byte("Content-Length: ")
|
||||||
// enough to realize this doesn't mutate h.
|
headerDate = []byte("Date: ")
|
||||||
func (h extraHeader) Write(w io.Writer) {
|
)
|
||||||
for i, v := range []string{h.contentType, h.contentLength, h.connection, h.date, h.transferEncoding} {
|
|
||||||
|
// 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 != "" {
|
if v != "" {
|
||||||
w.Write(extraHeaderKeys[i])
|
w.Write(extraHeaderKeys[i])
|
||||||
w.Write(colonSpace)
|
w.Write(colonSpace)
|
||||||
io.WriteString(w, v)
|
w.WriteString(v)
|
||||||
w.Write(crlf)
|
w.Write(crlf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -694,7 +737,7 @@ func (cw *chunkWriter) writeHeader(p []byte) {
|
|||||||
// "keep-alive" connections alive.
|
// "keep-alive" connections alive.
|
||||||
if w.handlerDone && header.get("Content-Length") == "" && w.req.Method != "HEAD" {
|
if w.handlerDone && header.get("Content-Length") == "" && w.req.Method != "HEAD" {
|
||||||
w.contentLength = int64(len(p))
|
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
|
// 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 {
|
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")
|
te := header.get("Transfer-Encoding")
|
||||||
@ -806,7 +849,7 @@ func (cw *chunkWriter) writeHeader(p []byte) {
|
|||||||
|
|
||||||
io.WriteString(w.conn.buf, statusLine(w.req, code))
|
io.WriteString(w.conn.buf, statusLine(w.req, code))
|
||||||
cw.header.WriteSubset(w.conn.buf, excludeHeader)
|
cw.header.WriteSubset(w.conn.buf, excludeHeader)
|
||||||
setHeader.Write(w.conn.buf)
|
setHeader.Write(w.conn.buf.Writer)
|
||||||
w.conn.buf.Write(crlf)
|
w.conn.buf.Write(crlf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user