@@ -15,47 +15,69 @@ import (
15
15
)
16
16
17
17
const defaultBufSize = 4096
18
+ const maxCachedBufSize = 16 * 1024
18
19
19
20
// A buffer which is used for both reading and writing.
20
21
// This is possible since communication on each connection is synchronous.
21
22
// In other words, we can't write and read simultaneously on the same connection.
22
23
// The buffer is similar to bufio.Reader / Writer but zero-copy-ish
23
24
// Also highly optimized for this particular use case.
25
+ // This buffer is backed by two byte slices in a double-buffering scheme
24
26
type buffer struct {
25
27
buf []byte // buf is a byte buffer who's length and capacity are equal.
26
28
nc net.Conn
27
29
idx int
28
30
length int
29
31
timeout time.Duration
32
+ dbuf [2 ][]byte // dbuf is an array with the two byte slices that back this buffer
33
+ flipcnt uint // flipccnt is the current buffer counter for double-buffering
30
34
}
31
35
32
36
// newBuffer allocates and returns a new buffer.
33
37
func newBuffer (nc net.Conn ) buffer {
38
+ fg := make ([]byte , defaultBufSize )
34
39
return buffer {
35
- buf : make ([]byte , defaultBufSize ),
36
- nc : nc ,
40
+ buf : fg ,
41
+ nc : nc ,
42
+ dbuf : [2 ][]byte {fg , nil },
37
43
}
38
44
}
39
45
46
+ // flip replaces the active buffer with the background buffer
47
+ // this is a delayed flip that simply increases the buffer counter;
48
+ // the actual flip will be performed the next time we call `buffer.fill`
49
+ func (b * buffer ) flip () {
50
+ b .flipcnt += 1
51
+ }
52
+
40
53
// fill reads into the buffer until at least _need_ bytes are in it
41
54
func (b * buffer ) fill (need int ) error {
42
55
n := b .length
56
+ // fill data into its double-buffering target: if we've called
57
+ // flip on this buffer, we'll be copying to the background buffer,
58
+ // and then filling it with network data; otherwise we'll just move
59
+ // the contents of the current buffer to the front before filling it
60
+ dest := b .dbuf [b .flipcnt & 1 ]
61
+
62
+ // grow buffer if necessary to fit the whole packet.
63
+ if need > len (dest ) {
64
+ // Round up to the next multiple of the default size
65
+ dest = make ([]byte , ((need / defaultBufSize )+ 1 )* defaultBufSize )
43
66
44
- // move existing data to the beginning
45
- if n > 0 && b .idx > 0 {
46
- copy (b .buf [0 :n ], b .buf [b .idx :])
67
+ // if the allocated buffer is not too large, move it to backing storage
68
+ // to prevent extra allocations on applications that perform large reads
69
+ if len (dest ) <= maxCachedBufSize {
70
+ b .dbuf [b .flipcnt & 1 ] = dest
71
+ }
47
72
}
48
73
49
- // grow buffer if necessary
50
- // TODO: let the buffer shrink again at some point
51
- // Maybe keep the org buf slice and swap back?
52
- if need > len (b .buf ) {
53
- // Round up to the next multiple of the default size
54
- newBuf := make ([]byte , ((need / defaultBufSize )+ 1 )* defaultBufSize )
55
- copy (newBuf , b .buf )
56
- b .buf = newBuf
74
+ // if we're filling the fg buffer, move the existing data to the start of it.
75
+ // if we're filling the bg buffer, copy over the data
76
+ if n > 0 {
77
+ copy (dest [0 :n ], b .buf [b .idx :])
57
78
}
58
79
80
+ b .buf = dest
59
81
b .idx = 0
60
82
61
83
for {
0 commit comments