Skip to content

Commit bd15c6b

Browse files
committed
TUN-3208: Reduce copies and allocations on h2mux write path. Pre-allocate 16KB write buffer on the first write if possible. Use explicit byte array for chunks on write thread to avoid copying through intermediate buffer due to io.CopyN.
benchmark old ns/op new ns/op delta BenchmarkSingleStreamLargeResponseBody-8 17786594 12163494 -31.61% benchmark old allocs new allocs delta BenchmarkSingleStreamLargeResponseBody-8 17086 15869 -7.12% benchmark old bytes new bytes delta BenchmarkSingleStreamLargeResponseBody-8 58215169 21604391 -62.89%
1 parent 42fe2e7 commit bd15c6b

File tree

2 files changed

+37
-9
lines changed

2 files changed

+37
-9
lines changed

h2mux/h2mux.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ import (
77
"sync"
88
"time"
99

10-
"github.com/cloudflare/cloudflared/logger"
1110
"github.com/prometheus/client_golang/prometheus"
1211
"golang.org/x/net/http2"
1312
"golang.org/x/net/http2/hpack"
1413
"golang.org/x/sync/errgroup"
14+
15+
"github.com/cloudflare/cloudflared/logger"
1516
)
1617

1718
const (
@@ -21,6 +22,7 @@ const (
2122
defaultTimeout time.Duration = 5 * time.Second
2223
defaultRetries uint64 = 5
2324
defaultWriteBufferMaxLen int = 1024 * 1024 // 1mb
25+
writeBufferInitialSize int = 16 * 1024 // 16KB
2426

2527
SettingMuxerMagic http2.SettingID = 0x42db
2628
MuxerMagicOrigin uint32 = 0xa2e43c8b
@@ -206,9 +208,9 @@ func Handshake(
206208
initialStreamWindow: m.config.DefaultWindowSize,
207209
streamWindowMax: m.config.MaxWindowSize,
208210
streamWriteBufferMaxLen: m.config.StreamWriteBufferMaxLen,
209-
r: m.r,
210-
metricsUpdater: m.muxMetricsUpdater,
211-
bytesRead: inBoundCounter,
211+
r: m.r,
212+
metricsUpdater: m.muxMetricsUpdater,
213+
bytesRead: inBoundCounter,
212214
}
213215
m.muxWriter = &MuxWriter{
214216
f: m.f,

h2mux/muxedstream.go

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,14 @@ func (s *MuxedStream) Write(p []byte) (int, error) {
138138
if s.writeEOF {
139139
return 0, io.EOF
140140
}
141+
142+
// pre-allocate some space in the write buffer if possible
143+
if buffer, ok := s.writeBuffer.(*bytes.Buffer); ok {
144+
if buffer.Cap() == 0 {
145+
buffer.Grow(writeBufferInitialSize)
146+
}
147+
}
148+
141149
totalWritten := 0
142150
for totalWritten < len(p) {
143151
// If the buffer is full, block till there is more room.
@@ -367,7 +375,9 @@ type streamChunk struct {
367375
// true if data frames should be sent
368376
sendData bool
369377
eof bool
370-
buffer bytes.Buffer
378+
379+
buffer []byte
380+
offset int
371381
}
372382

373383
// getChunk atomically extracts a chunk of data to be written by MuxWriter.
@@ -385,8 +395,17 @@ func (s *MuxedStream) getChunk() *streamChunk {
385395
eof: s.writeEOF && uint32(s.writeBuffer.Len()) <= s.sendWindow,
386396
}
387397
// Copy at most s.sendWindow bytes, adjust the sendWindow accordingly
388-
writeLen, _ := io.CopyN(&chunk.buffer, s.writeBuffer, int64(s.sendWindow))
389-
s.sendWindow -= uint32(writeLen)
398+
toCopy := int(s.sendWindow)
399+
if toCopy > s.writeBuffer.Len() {
400+
toCopy = s.writeBuffer.Len()
401+
}
402+
403+
if toCopy > 0 {
404+
buf := make([]byte, toCopy)
405+
writeLen, _ := s.writeBuffer.Read(buf)
406+
chunk.buffer = buf[:writeLen]
407+
s.sendWindow -= uint32(writeLen)
408+
}
390409

391410
// Allow MuxedStream::Write() to continue, if needed
392411
if s.writeBuffer.Len() < s.writeBufferMaxLen {
@@ -421,8 +440,15 @@ func (c *streamChunk) sendDataFrame() bool {
421440
}
422441

423442
func (c *streamChunk) nextDataFrame(frameSize int) (payload []byte, endStream bool) {
424-
payload = c.buffer.Next(frameSize)
425-
if c.buffer.Len() == 0 {
443+
bytesLeft := len(c.buffer) - c.offset
444+
if frameSize > bytesLeft {
445+
frameSize = bytesLeft
446+
}
447+
nextOffset := c.offset + frameSize
448+
payload = c.buffer[c.offset:nextOffset]
449+
c.offset = nextOffset
450+
451+
if c.offset == len(c.buffer) {
426452
// this is the last data frame in this chunk
427453
c.sendData = false
428454
if c.eof {

0 commit comments

Comments
 (0)