@@ -15,47 +15,70 @@ 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 )
39
+ bg := make ([]byte , defaultBufSize )
34
40
return buffer {
35
- buf : make ([]byte , defaultBufSize ),
36
- nc : nc ,
41
+ buf : fg ,
42
+ nc : nc ,
43
+ dbuf : [2 ][]byte {fg , bg },
37
44
}
38
45
}
39
46
47
+ // flip replaces the active buffer with the background buffer
48
+ // this is a delayed flip that simply increases the buffer counter;
49
+ // the actual flip will be performed the next time we call `buffer.fill`
50
+ func (b * buffer ) flip () {
51
+ b .flipcnt += 1
52
+ }
53
+
40
54
// fill reads into the buffer until at least _need_ bytes are in it
41
55
func (b * buffer ) fill (need int ) error {
42
56
n := b .length
57
+ // fill data into its double-buffering target: if we've called
58
+ // flip on this buffer, we'll be copying to the background buffer,
59
+ // and then filling it with network data; otherwise we'll just move
60
+ // the contents of the current buffer to the front before filling it
61
+ dest := b .dbuf [b .flipcnt & 1 ]
62
+
63
+ // grow buffer if necessary to fit the whole packet.
64
+ if need > len (dest ) {
65
+ // Round up to the next multiple of the default size
66
+ dest = make ([]byte , ((need / defaultBufSize )+ 1 )* defaultBufSize )
43
67
44
- // move existing data to the beginning
45
- if n > 0 && b .idx > 0 {
46
- copy (b .buf [0 :n ], b .buf [b .idx :])
68
+ // if the allocated buffer is not too large, move it to backing storage
69
+ // to prevent extra allocations on applications that perform large reads
70
+ if len (dest ) <= maxCachedBufSize {
71
+ b .dbuf [b .flipcnt & 1 ] = dest
72
+ }
47
73
}
48
74
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
75
+ // if we're filling the fg buffer, move the existing data to the start of it.
76
+ // if we're filling the bg buffer, copy over the data
77
+ if n > 0 {
78
+ copy (dest [0 :n ], b .buf [b .idx :])
57
79
}
58
80
81
+ b .buf = dest
59
82
b .idx = 0
60
83
61
84
for {
0 commit comments