Skip to content

Commit

Permalink
Add a simple byte buffer
Browse files Browse the repository at this point in the history
Replace byte.Buffer usage with sys.ByteBuffer which is faster for the operations we need.

```
BenchmarkByteBuffer/byteBuffer-4            200000000            8.89 ns/op        0 B/op          0 allocs/op
BenchmarkByteBuffer/bytes.Buffer-4          50000000            28.0 ns/op         0 B/op          0 allocs/op
BenchmarkByteBufferGrow/byteBuffer-4        100000000           15.7 ns/op         2 B/op          0 allocs/op
BenchmarkByteBufferGrow/bytes.Buffer-4      100000000           24.1 ns/op         2 B/op          0 allocs/op
```
  • Loading branch information
andrewkroh committed Dec 6, 2016
1 parent 516a307 commit b0f98f6
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 3 deletions.
5 changes: 2 additions & 3 deletions winlogbeat/eventlog/wineventlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
package eventlog

import (
"bytes"
"fmt"
"io"
"syscall"
Expand Down Expand Up @@ -79,7 +78,7 @@ type winEventLog struct {

render func(event win.EvtHandle, out io.Writer) error // Function for rendering the event to XML.
renderBuf []byte // Buffer used for rendering event.
outputBuf *bytes.Buffer // Buffer for receiving XML
outputBuf *sys.ByteBuffer // Buffer for receiving XML
cache *messageFilesCache // Cached mapping of source name to event message file handles.

logPrefix string // String to prefix on log messages.
Expand Down Expand Up @@ -275,7 +274,7 @@ func newWinEventLog(options map[string]interface{}) (EventLog, error) {
channelName: c.Name,
maxRead: c.BatchReadSize,
renderBuf: make([]byte, renderBufferSize),
outputBuf: bytes.NewBuffer(make([]byte, renderBufferSize)),
outputBuf: sys.NewByteBuffer(renderBufferSize),
cache: newMessageFilesCache(c.Name, eventMetadataHandle, freeHandle),
logPrefix: fmt.Sprintf("WinEventLog[%s]", c.Name),
eventMetadata: c.EventMetadata,
Expand Down
46 changes: 46 additions & 0 deletions winlogbeat/sys/buffer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package sys

// ByteBuffer is an expandable buffer backed by a byte slice.
type ByteBuffer struct {
buf []byte
offset int
}

// NewByteBuffer creates a new ByteBuffer with an initial capacity of
// initialSize.
func NewByteBuffer(initialSize int) *ByteBuffer {
return &ByteBuffer{buf: make([]byte, initialSize)}
}

// Write appends the contents of p to the buffer, growing the buffer as needed.
// The return value is the length of p; err is always nil.
func (b *ByteBuffer) Write(p []byte) (int, error) {
if len(b.buf) < b.offset+len(p) {
// Create a buffer larger than needed so we don't spend lots of time
// allocating and copying.
spaceNeeded := len(b.buf) - b.offset + len(p)
largerBuf := make([]byte, 2*len(b.buf)+spaceNeeded)
copy(largerBuf, b.buf[:b.offset])
b.buf = largerBuf
}
n := copy(b.buf[b.offset:], p)
b.offset += n
return n, nil
}

// Reset resets the buffer to be empty. It retains the same underlying storage.
func (b *ByteBuffer) Reset() {
b.offset = 0
b.buf = b.buf[:cap(b.buf)]
}

// Bytes returns a slice of length b.Len() holding the bytes that have been
// written to the buffer.
func (b *ByteBuffer) Bytes() []byte {
return b.buf[:b.offset]
}

// Len returns the number of bytes that have been written to the buffer.
func (b *ByteBuffer) Len() int {
return b.offset
}
102 changes: 102 additions & 0 deletions winlogbeat/sys/buffer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package sys

import (
"bytes"
"io"
"testing"

"github.com/stretchr/testify/assert"
)

var _ io.Writer = &ByteBuffer{}

func TestByteBuffer(t *testing.T) {
input := "hello"
length := len(input)
buf := NewByteBuffer(1024)

n, err := buf.Write([]byte(input))
if err != nil {
t.Fatal(err)
}
assert.Equal(t, length, n)

assert.Equal(t, input, string(buf.Bytes()))
assert.Equal(t, length, len(buf.Bytes()))
assert.Equal(t, length, buf.Len())
}

func TestByteBufferGrow(t *testing.T) {
input := "hello"
length := len(input)
buf := NewByteBuffer(0)

n, err := buf.Write([]byte(input))
if err != nil {
t.Fatal(err)
}
assert.Equal(t, length, n)

assert.Equal(t, input, string(buf.Bytes()))
assert.Equal(t, length, len(buf.Bytes()))
assert.Equal(t, length, buf.Len())
assert.Equal(t, length, len(buf.buf))

n, err = buf.Write([]byte(input))
if err != nil {
t.Fatal(err)
}
assert.Equal(t, length, n)

assert.Equal(t, input+input, string(buf.Bytes()))
assert.Equal(t, 2*length, len(buf.Bytes()))
assert.Equal(t, 2*length, buf.Len())
}

func BenchmarkByteBuffer(b *testing.B) {
input := []byte("test writing this sentence to a buffer")

b.Run("byteBuffer", func(b *testing.B) {
buf := NewByteBuffer(1024)
b.ResetTimer()

for i := 0; i < b.N; i++ {
buf.Write(input)
buf.Bytes()
buf.Reset()
}
})

b.Run("bytes.Buffer", func(b *testing.B) {
buf := bytes.NewBuffer(make([]byte, 0, 1024))
b.ResetTimer()

for i := 0; i < b.N; i++ {
buf.Write(input)
buf.Bytes()
buf.Reset()
}
})
}

func BenchmarkByteBufferGrow(b *testing.B) {
b.Run("byteBuffer", func(b *testing.B) {
buf := NewByteBuffer(0)
b.ResetTimer()

for i := 0; i < b.N; i++ {
buf.Write([]byte("a"))
buf.Bytes()
}
})

b.Run("bytes.Buffer", func(b *testing.B) {
buf := bytes.NewBuffer(make([]byte, 0))
b.ResetTimer()

for i := 0; i < b.N; i++ {
buf.Write([]byte("a"))
buf.Bytes()
}
})
}

0 comments on commit b0f98f6

Please sign in to comment.