Skip to content

Commit 188746f

Browse files
Allow buffer reuse in ReadHeader/WriteHeader variants.
1 parent 36a216a commit 188746f

4 files changed

Lines changed: 68 additions & 6 deletions

File tree

read.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,18 @@ func ReadHeader(r io.Reader) (h Header, err error) {
2121
// So 14 - 2 = 12.
2222
bts := make([]byte, 2, MaxHeaderSize-2)
2323

24+
return ReadHeaderBuffer(r, bts)
25+
}
26+
27+
// ReadHeaderBuffer reads a frame header from r using user-provided buffer.
28+
// Provided buffer must be at least 12 bytes long.
29+
func ReadHeaderBuffer(r io.Reader, bts []byte) (h Header, err error) {
30+
if cap(bts) < MaxHeaderSize-2 {
31+
return h, io.ErrShortBuffer
32+
}
33+
34+
bts = bts[:2]
35+
2436
// Prepare to hold first 2 bytes to choose size of next read.
2537
_, err = io.ReadFull(r, bts)
2638
if err != nil {

read_test.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,40 @@ func TestReadHeader(t *testing.T) {
3838
}
3939

4040
func BenchmarkReadHeader(b *testing.B) {
41+
setup := func(header Header, n int) (rds []io.Reader) {
42+
bts := MustCompileFrame(Frame{Header: header})
43+
rds = make([]io.Reader, n)
44+
for i := 0; i < n; i++ {
45+
rds[i] = bytes.NewReader(bts)
46+
}
47+
48+
return
49+
}
50+
4151
for i, bench := range RWBenchCases {
4252
b.Run(fmt.Sprintf("%s#%d", bench.label, i), func(b *testing.B) {
43-
bts := MustCompileFrame(Frame{Header: bench.header})
44-
rds := make([]io.Reader, b.N)
53+
rds := setup(bench.header, b.N)
54+
55+
b.ReportAllocs()
56+
b.ResetTimer()
57+
4558
for i := 0; i < b.N; i++ {
46-
rds[i] = bytes.NewReader(bts)
59+
_, err := ReadHeader(rds[i])
60+
if err != nil {
61+
b.Fatal(err)
62+
}
4763
}
64+
})
65+
66+
b.Run(fmt.Sprintf("reused-buffer-%s#%d", bench.label, i), func(b *testing.B) {
67+
rds := setup(bench.header, b.N)
68+
bts := make([]byte, MaxHeaderSize)
4869

70+
b.ReportAllocs()
4971
b.ResetTimer()
5072

5173
for i := 0; i < b.N; i++ {
52-
_, err := ReadHeader(rds[i])
74+
_, err := ReadHeaderBuffer(rds[i], bts)
5375
if err != nil {
5476
b.Fatal(err)
5577
}

write.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,23 @@ func WriteHeader(w io.Writer, h Header) error {
5050
// Make slice of bytes with capacity 14 that could hold any header.
5151
bts := make([]byte, MaxHeaderSize)
5252

53+
return WriteHeaderBuffer(w, h, bts)
54+
}
55+
56+
// WriteHeaderBuffer writes header binary representation into w using user-provided buffer.
57+
// Provided buffer must be at least 14 bytes long.
58+
func WriteHeaderBuffer(w io.Writer, h Header, bts []byte) error {
59+
if cap(bts) < MaxHeaderSize {
60+
return io.ErrShortBuffer
61+
}
62+
63+
bts = bts[:MaxHeaderSize]
64+
65+
bts[0] = h.Rsv<<4 | byte(h.OpCode)
66+
5367
if h.Fin {
5468
bts[0] |= bit0
5569
}
56-
bts[0] |= h.Rsv << 4
57-
bts[0] |= byte(h.OpCode)
5870

5971
var n int
6072
switch {

write_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,27 @@ func TestWriteHeader(t *testing.T) {
3131
func BenchmarkWriteHeader(b *testing.B) {
3232
for _, bench := range RWBenchCases {
3333
b.Run(bench.label, func(b *testing.B) {
34+
b.ReportAllocs()
35+
b.ResetTimer()
36+
3437
for i := 0; i < b.N; i++ {
3538
if err := WriteHeader(ioutil.Discard, bench.header); err != nil {
3639
b.Fatal(err)
3740
}
3841
}
3942
})
43+
44+
b.Run("reused-buffer-"+bench.label, func(b *testing.B) {
45+
bts := make([]byte, MaxHeaderSize)
46+
47+
b.ReportAllocs()
48+
b.ResetTimer()
49+
50+
for i := 0; i < b.N; i++ {
51+
if err := WriteHeaderBuffer(ioutil.Discard, bench.header, bts); err != nil {
52+
b.Fatal(err)
53+
}
54+
}
55+
})
4056
}
4157
}

0 commit comments

Comments
 (0)