From b0f98f67fe052ee67594125c4cf5cdd38428476d Mon Sep 17 00:00:00 2001 From: Andrew Kroh Date: Tue, 6 Dec 2016 18:07:12 -0500 Subject: [PATCH] Add a simple byte buffer 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 ``` --- winlogbeat/eventlog/wineventlog.go | 5 +- winlogbeat/sys/buffer.go | 46 +++++++++++++ winlogbeat/sys/buffer_test.go | 102 +++++++++++++++++++++++++++++ 3 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 winlogbeat/sys/buffer.go create mode 100644 winlogbeat/sys/buffer_test.go diff --git a/winlogbeat/eventlog/wineventlog.go b/winlogbeat/eventlog/wineventlog.go index 760e714dd7a..69a3b8abbc0 100644 --- a/winlogbeat/eventlog/wineventlog.go +++ b/winlogbeat/eventlog/wineventlog.go @@ -3,7 +3,6 @@ package eventlog import ( - "bytes" "fmt" "io" "syscall" @@ -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. @@ -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, diff --git a/winlogbeat/sys/buffer.go b/winlogbeat/sys/buffer.go new file mode 100644 index 00000000000..799a383df40 --- /dev/null +++ b/winlogbeat/sys/buffer.go @@ -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 +} diff --git a/winlogbeat/sys/buffer_test.go b/winlogbeat/sys/buffer_test.go new file mode 100644 index 00000000000..825d916f232 --- /dev/null +++ b/winlogbeat/sys/buffer_test.go @@ -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() + } + }) +}