Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified LICENSE
100755 → 100644
Empty file.
38 changes: 38 additions & 0 deletions anybuf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package circbuf

import (
"fmt"
)

// anyBuffer implements a circular buffer of any size.
type anyBuffer struct {
baseBuffer
}

// Write writes up to len(buf) bytes to the internal ring,
// overriding older data if necessary.
func (b *anyBuffer) Write(buf []byte) (int, error) {
n := b.write(buf)
b.writeCursor = ((b.writeCursor + n) % b.size)
return len(buf), nil
}

// WriteByte writes a single byte into the buffer.
func (b *anyBuffer) WriteByte(c byte) error {
b.data[b.writeCursor] = c
b.writeCursor = ((b.writeCursor + 1) % b.size)
b.written++
return nil
}

// Get returns a single byte out of the buffer, at the given position.
func (b *anyBuffer) Get(i int64) (byte, error) {
switch {
case i >= b.written || i >= b.size:
return 0, fmt.Errorf("Index out of bounds: %v", i)
case b.written > b.size:
return b.data[(b.writeCursor+i)%b.size], nil
default:
return b.data[i], nil
}
}
71 changes: 71 additions & 0 deletions basebuf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package circbuf

type baseBuffer struct {
data []byte
out []byte
size int64
writeCursor int64
written int64
}

// Size returns the size of the buffer
func (b *baseBuffer) Size() int64 {
return b.size
}

// TotalWritten provides the total number of bytes written
func (b *baseBuffer) TotalWritten() int64 {
return b.written
}

// Bytes provides a slice of the bytes written. This
// slice should not be written to. The underlying array
// may point to data that will be overwritten by a subsequent
// call to Bytes. It does no allocation.
func (b *baseBuffer) Bytes() []byte {
switch {
case b.written >= b.size && b.writeCursor == 0:
return b.data
case b.written > b.size:
copy(b.out, b.data[b.writeCursor:])
copy(b.out[b.size-b.writeCursor:], b.data[:b.writeCursor])
return b.out
default:
return b.data[:b.writeCursor]
}
}

// Reset resets the buffer so it has no content.
func (b *baseBuffer) Reset() {
b.writeCursor = 0
b.written = 0
}

// String returns the contents of the buffer as a string
func (b *baseBuffer) String() string {
return string(b.Bytes())
}

// write writes len(buf) bytes to the circular buffer and returns by how much
// the writeCursor must be incremented. (This function does not increment the
// writeCursor!)
func (b *baseBuffer) write(buf []byte) int64 {
// Account for total bytes written
n := len(buf)
b.written += int64(n)

// If the buffer is larger than ours, then we only care
// about the last size bytes anyways
if int64(n) > b.size {
buf = buf[int64(n)-b.size:]
}

// Copy in place
remain := b.size - b.writeCursor
copy(b.data[b.writeCursor:], buf)
if int64(len(buf)) > remain {
copy(b.data, buf[remain:])
}

return int64(len(buf))
}
148 changes: 148 additions & 0 deletions benchmark_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package circbuf

import (
"testing"
)

// k is the number of repetitions per operation.
const k = 1000

// benchmarkWrite benchmarks Write, by writing writeSize to a Buffer that is
// bufSize bytes long.
func benchmarkWrite(b *testing.B, bufSize, writeSize int64) {
data := make([]byte, writeSize)
buf, err := NewBuffer(bufSize)
if err != nil {
b.Fatalf("creating buffer of size %v: %v", bufSize, err)
}
b.SetBytes(k * writeSize)

for i := 0; i < b.N; i++ {
for j := 0; j < k; j++ {
_, _ = buf.Write(data)
}
}
}

func Benchmark_Write_1024_500(b *testing.B) {
benchmarkWrite(b, 1024, 500)
}

func Benchmark_Write_1025_500(b *testing.B) {
benchmarkWrite(b, 1025, 500)
}

func Benchmark_Write_1024_5000(b *testing.B) {
benchmarkWrite(b, 1024, 5000)
}

func Benchmark_Write_1025_5000(b *testing.B) {
benchmarkWrite(b, 1025, 5000)
}

func Benchmark_Write_65536_5000(b *testing.B) {
benchmarkWrite(b, 65536, 5000)
}

func Benchmark_Write_65537_5000(b *testing.B) {
benchmarkWrite(b, 65537, 5000)
}

func Benchmark_Write_1024_5(b *testing.B) {
benchmarkWrite(b, 1024, 5)
}

func Benchmark_Write_1025_5(b *testing.B) {
benchmarkWrite(b, 1025, 5)
}

// benchmarkWriteByte benchmarks WriteByte, on a Buffer that is bufSize bytes
// long.
func benchmarkWriteByte(b *testing.B, bufSize int64) {
buf, err := NewBuffer(bufSize)
if err != nil {
b.Fatalf("creating buffer of size %v: %v", bufSize, err)
}
b.SetBytes(k)

for i := 0; i < b.N; i++ {
for j := 0; j < k; j++ {
_ = buf.WriteByte(0xba)
}
}
}

func Benchmark_WriteByte_1024(b *testing.B) {
benchmarkWriteByte(b, 1024)
}

func Benchmark_WriteByte_1025(b *testing.B) {
benchmarkWriteByte(b, 1025)
}

func Benchmark_WriteByte_65536(b *testing.B) {
benchmarkWriteByte(b, 65536)
}

func Benchmark_WriteByte_65537(b *testing.B) {
benchmarkWriteByte(b, 65537)
}

// benchmarkGet benchmarks Get with a buffer that is bufSize bytes long and was
// written to proportionally to fillRatio (so, for example, fillRatio == 0.5
// means write to half of the buffer, by fillRatio == 1 means write to every
// byte of the buffer, and fillRatio == 2 means write twice as much data as it
// fits in the buffer).
func benchmarkGet(b *testing.B, bufSize int64, fillRatio float64) {
buf, err := NewBuffer(bufSize)
if err != nil {
b.Fatalf("creating buffer of size %v: %v", bufSize, err)
}

writeSize := int64(float64(bufSize) * fillRatio)
data := make([]byte, writeSize)
_, err = buf.Write(data)
if err != nil {
b.Fatalf("writing data to buffer: %v", err)
}

readLimit := bufSize
if bufSize > writeSize {
readLimit = writeSize
}

b.SetBytes(k)
b.ResetTimer()

for i := 0; i < b.N; i++ {
for j := 0; j < k; j++ {
for h := int64(0); h < readLimit; h++ {
_, _ = buf.Get(h)
}
}
}
}

func Benchmark_Get_HalfFull_1024(b *testing.B) {
benchmarkGet(b, 1024, 0.5)
}

func Benchmark_Get_HalfFull_1025(b *testing.B) {
benchmarkGet(b, 1025, 0.5)
}

func Benchmark_Get_Full_1024(b *testing.B) {
benchmarkGet(b, 1024, 1.0)
}

func Benchmark_Get_Full_1025(b *testing.B) {
benchmarkGet(b, 1025, 1.0)
}

func Benchmark_Get_TwiceFull_1024(b *testing.B) {
benchmarkGet(b, 1024, 2.0)
}

func Benchmark_Get_TwiceFull_1025(b *testing.B) {
benchmarkGet(b, 1025, 2.0)
}
Loading