Skip to content

Commit e7b1435

Browse files
committed
http2: optimize server frame writes
Don't do async frame writes when the write is known to be small enough to definitely fit in the write buffer and not cause a flush (which might block). This avoids starting a new goroutine and doing a channel send operation for many frame writes. name old time/op new time/op delta ServerGets-4 146µs ± 2% 144µs ± 1% -1.46% (p=0.000 n=14+14) ServerPosts-4 162µs ± 1% 160µs ± 1% -1.15% (p=0.000 n=15+15) Server_GetRequest-4 145µs ± 1% 143µs ± 0% -1.26% (p=0.000 n=15+15) Server_PostRequest-4 160µs ± 1% 158µs ± 1% -1.32% (p=0.000 n=15+15) The headers frame is the main last one which might show some benefit if there's a cheap enough way to conservatively calculate its size. Change-Id: I9be2ddbf04689340819d8701ea671fff378d9e79 Reviewed-on: https://go-review.googlesource.com/31495 Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent 4be9b97 commit e7b1435

File tree

3 files changed

+67
-4
lines changed

3 files changed

+67
-4
lines changed

http2/http2.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,14 +254,27 @@ func newBufferedWriter(w io.Writer) *bufferedWriter {
254254
return &bufferedWriter{w: w}
255255
}
256256

257+
// bufWriterPoolBufferSize is the size of bufio.Writer's
258+
// buffers created using bufWriterPool.
259+
//
260+
// TODO: pick a less arbitrary value? this is a bit under
261+
// (3 x typical 1500 byte MTU) at least. Other than that,
262+
// not much thought went into it.
263+
const bufWriterPoolBufferSize = 4 << 10
264+
257265
var bufWriterPool = sync.Pool{
258266
New: func() interface{} {
259-
// TODO: pick something better? this is a bit under
260-
// (3 x typical 1500 byte MTU) at least.
261-
return bufio.NewWriterSize(nil, 4<<10)
267+
return bufio.NewWriterSize(nil, bufWriterPoolBufferSize)
262268
},
263269
}
264270

271+
func (w *bufferedWriter) Available() int {
272+
if w.bw == nil {
273+
return bufWriterPoolBufferSize
274+
}
275+
return w.bw.Available()
276+
}
277+
265278
func (w *bufferedWriter) Write(p []byte) (n int, err error) {
266279
if w.bw == nil {
267280
bw := bufWriterPool.Get().(*bufio.Writer)

http2/server.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -884,7 +884,12 @@ func (sc *serverConn) startFrameWrite(wr FrameWriteRequest) {
884884

885885
sc.writingFrame = true
886886
sc.needsFrameFlush = true
887-
go sc.writeFrameAsync(wr)
887+
if wr.write.staysWithinBuffer(sc.bw.Available()) {
888+
err := wr.write.writeFrame(sc)
889+
sc.wroteFrame(frameWriteResult{wr, err})
890+
} else {
891+
go sc.writeFrameAsync(wr)
892+
}
888893
}
889894

890895
// errHandlerPanicked is the error given to any callers blocked in a read from

http2/write.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ import (
1818
// writeFramer is implemented by any type that is used to write frames.
1919
type writeFramer interface {
2020
writeFrame(writeContext) error
21+
22+
// staysWithinBuffer reports whether this writer promises that
23+
// it will only write less than or equal to size bytes, and it
24+
// won't Flush the write context.
25+
staysWithinBuffer(size int) bool
2126
}
2227

2328
// writeContext is the interface needed by the various frame writer
@@ -62,8 +67,16 @@ func (flushFrameWriter) writeFrame(ctx writeContext) error {
6267
return ctx.Flush()
6368
}
6469

70+
func (flushFrameWriter) staysWithinBuffer(max int) bool { return false }
71+
6572
type writeSettings []Setting
6673

74+
func (s writeSettings) staysWithinBuffer(max int) bool {
75+
const settingSize = 6 // uint16 + uint32
76+
return frameHeaderLen+settingSize*len(s) <= max
77+
78+
}
79+
6780
func (s writeSettings) writeFrame(ctx writeContext) error {
6881
return ctx.Framer().WriteSettings([]Setting(s)...)
6982
}
@@ -83,6 +96,8 @@ func (p *writeGoAway) writeFrame(ctx writeContext) error {
8396
return err
8497
}
8598

99+
func (*writeGoAway) staysWithinBuffer(max int) bool { return false } // flushes
100+
86101
type writeData struct {
87102
streamID uint32
88103
p []byte
@@ -97,6 +112,10 @@ func (w *writeData) writeFrame(ctx writeContext) error {
97112
return ctx.Framer().WriteData(w.streamID, w.endStream, w.p)
98113
}
99114

115+
func (w *writeData) staysWithinBuffer(max int) bool {
116+
return frameHeaderLen+len(w.p) <= max
117+
}
118+
100119
// handlerPanicRST is the message sent from handler goroutines when
101120
// the handler panics.
102121
type handlerPanicRST struct {
@@ -107,22 +126,30 @@ func (hp handlerPanicRST) writeFrame(ctx writeContext) error {
107126
return ctx.Framer().WriteRSTStream(hp.StreamID, ErrCodeInternal)
108127
}
109128

129+
func (hp handlerPanicRST) staysWithinBuffer(max int) bool { return frameHeaderLen+4 <= max }
130+
110131
func (se StreamError) writeFrame(ctx writeContext) error {
111132
return ctx.Framer().WriteRSTStream(se.StreamID, se.Code)
112133
}
113134

135+
func (se StreamError) staysWithinBuffer(max int) bool { return frameHeaderLen+4 <= max }
136+
114137
type writePingAck struct{ pf *PingFrame }
115138

116139
func (w writePingAck) writeFrame(ctx writeContext) error {
117140
return ctx.Framer().WritePing(true, w.pf.Data)
118141
}
119142

143+
func (w writePingAck) staysWithinBuffer(max int) bool { return frameHeaderLen+len(w.pf.Data) <= max }
144+
120145
type writeSettingsAck struct{}
121146

122147
func (writeSettingsAck) writeFrame(ctx writeContext) error {
123148
return ctx.Framer().WriteSettingsAck()
124149
}
125150

151+
func (writeSettingsAck) staysWithinBuffer(max int) bool { return frameHeaderLen <= max }
152+
126153
// writeResHeaders is a request to write a HEADERS and 0+ CONTINUATION frames
127154
// for HTTP response headers or trailers from a server handler.
128155
type writeResHeaders struct {
@@ -144,6 +171,17 @@ func encKV(enc *hpack.Encoder, k, v string) {
144171
enc.WriteField(hpack.HeaderField{Name: k, Value: v})
145172
}
146173

174+
func (w *writeResHeaders) staysWithinBuffer(max int) bool {
175+
// TODO: this is a common one. It'd be nice to return true
176+
// here and get into the fast path if we could be clever and
177+
// calculate the size fast enough, or at least a conservative
178+
// uppper bound that usually fires. (Maybe if w.h and
179+
// w.trailers are nil, so we don't need to enumerate it.)
180+
// Otherwise I'm afraid that just calculating the length to
181+
// answer this question would be slower than the ~2µs benefit.
182+
return false
183+
}
184+
147185
func (w *writeResHeaders) writeFrame(ctx writeContext) error {
148186
enc, buf := ctx.HeaderEncoder()
149187
buf.Reset()
@@ -220,11 +258,18 @@ func (w write100ContinueHeadersFrame) writeFrame(ctx writeContext) error {
220258
})
221259
}
222260

261+
func (w write100ContinueHeadersFrame) staysWithinBuffer(max int) bool {
262+
// Sloppy but conservative:
263+
return 9+2*(len(":status")+len("100")) <= max
264+
}
265+
223266
type writeWindowUpdate struct {
224267
streamID uint32 // or 0 for conn-level
225268
n uint32
226269
}
227270

271+
func (wu writeWindowUpdate) staysWithinBuffer(max int) bool { return frameHeaderLen+4 <= max }
272+
228273
func (wu writeWindowUpdate) writeFrame(ctx writeContext) error {
229274
return ctx.Framer().WriteWindowUpdate(wu.streamID, wu.n)
230275
}

0 commit comments

Comments
 (0)