Skip to content

Commit 21bf76e

Browse files
committed
Initial implementation
0 parents  commit 21bf76e

10 files changed

Lines changed: 324 additions & 0 deletions

.travis.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
language: go
2+
3+
go:
4+
- 1.6
5+
6+
script:
7+
# build test for supported platforms
8+
- GOOS=linux go build
9+
- GOOS=darwin go build
10+
- GOOS=freebsd go build
11+
- GOOS=windows go build
12+
- GOARCH=386 go build
13+
14+
# run tests on a standard platform
15+
- go test -v ./...

LICENSE

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2016 Aliaksandr Valialkin, VertaMedia
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
22+

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[![Build Status](https://travis-ci.org/valyala/bytebufferpool.svg)](https://travis-ci.org/valyala/bytebufferpool)
2+
[![GoDoc](https://godoc.org/github.com/valyala/bytebufferpool?status.svg)](http://godoc.org/github.com/valyala/bytebufferpool)
3+
[![Go Report](http://goreportcard.com/badge/valyala/bytebufferpool)](http://goreportcard.com/report/valyala/bytebufferpool)
4+
5+
# bytebufferpool
6+
7+
An implementation of a pool of byte buffers with anti-fragmentation protection.
8+
9+
The pool may waste limited amount of memory due to fragmentation.
10+
This amount equals to the maximum total size of the byte buffers
11+
in concurrent use.

bytebuffer.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package bytebufferpool
2+
3+
// ByteBuffer provides byte buffer, which can be used for minimizing
4+
// memory allocations.
5+
//
6+
// ByteBuffer may be used with functions appending data to the given []byte
7+
// slice. See example code for details.
8+
//
9+
// Use AcquireByteBuffer for obtaining an empty byte buffer.
10+
type ByteBuffer struct {
11+
12+
// B is a byte buffer to use in append-like workloads.
13+
// See example code for details.
14+
B []byte
15+
}
16+
17+
// Write implements io.Writer - it appends p to ByteBuffer.B
18+
func (b *ByteBuffer) Write(p []byte) (int, error) {
19+
b.B = append(b.B, p...)
20+
return len(p), nil
21+
}
22+
23+
// WriteString appends s to ByteBuffer.B
24+
func (b *ByteBuffer) WriteString(s string) (int, error) {
25+
b.B = append(b.B, s...)
26+
return len(s), nil
27+
}
28+
29+
// Set sets ByteBuffer.B to p
30+
func (b *ByteBuffer) Set(p []byte) {
31+
b.B = append(b.B[:0], p...)
32+
}
33+
34+
// SetString sets ByteBuffer.B to s
35+
func (b *ByteBuffer) SetString(s string) {
36+
b.B = append(b.B[:0], s...)
37+
}
38+
39+
// Reset makes ByteBuffer.B empty.
40+
func (b *ByteBuffer) Reset() {
41+
b.B = b.B[:0]
42+
}
43+
44+
// AcquireByteBuffer returns an empty byte buffer from the pool.
45+
//
46+
// Acquired byte buffer may be returned to the pool via ReleaseByteBuffer call.
47+
// This reduces the number of memory allocations required for byte buffer
48+
// management.
49+
func AcquireByteBuffer() *ByteBuffer {
50+
return defaultByteBufferPool.Acquire()
51+
}
52+
53+
// ReleaseByteBuffer returns byte buffer to the pool.
54+
//
55+
// ByteBuffer.B mustn't be touched after returning it to the pool.
56+
// Otherwise data races will occur.
57+
func ReleaseByteBuffer(b *ByteBuffer) {
58+
defaultByteBufferPool.Release(b)
59+
}
60+
61+
var defaultByteBufferPool byteBufferPool

bytebuffer_example_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package bytebufferpool_test
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/valyala/bytebufferpool"
7+
)
8+
9+
func ExampleByteBuffer() {
10+
bb := bytebufferpool.AcquireByteBuffer()
11+
12+
bb.WriteString("first line\n")
13+
bb.Write([]byte("second line\n"))
14+
bb.B = append(bb.B, "third line\n"...)
15+
16+
fmt.Printf("bytebuffer contents=%q", bb.B)
17+
18+
// It is safe to release byte buffer now, since it is
19+
// no longer used.
20+
bytebufferpool.ReleaseByteBuffer(bb)
21+
}

bytebuffer_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package bytebufferpool
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
"time"
7+
)
8+
9+
func TestByteBufferAcquireReleaseSerial(t *testing.T) {
10+
testByteBufferAcquireRelease(t)
11+
}
12+
13+
func TestByteBufferAcquireReleaseConcurrent(t *testing.T) {
14+
concurrency := 10
15+
ch := make(chan struct{}, concurrency)
16+
for i := 0; i < concurrency; i++ {
17+
go func() {
18+
testByteBufferAcquireRelease(t)
19+
ch <- struct{}{}
20+
}()
21+
}
22+
23+
for i := 0; i < concurrency; i++ {
24+
select {
25+
case <-ch:
26+
case <-time.After(time.Second):
27+
t.Fatalf("timeout!")
28+
}
29+
}
30+
}
31+
32+
func testByteBufferAcquireRelease(t *testing.T) {
33+
for i := 0; i < 10; i++ {
34+
expectedS := fmt.Sprintf("num %d", i)
35+
b := AcquireByteBuffer()
36+
b.B = append(b.B, "num "...)
37+
b.B = append(b.B, fmt.Sprintf("%d", i)...)
38+
if string(b.B) != expectedS {
39+
t.Fatalf("unexpected result: %q. Expecting %q", b.B, expectedS)
40+
}
41+
ReleaseByteBuffer(b)
42+
}
43+
}

bytebuffer_timing_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package bytebufferpool
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
)
7+
8+
func BenchmarkByteBufferWrite(b *testing.B) {
9+
s := []byte("foobarbaz")
10+
b.RunParallel(func(pb *testing.PB) {
11+
var buf ByteBuffer
12+
for pb.Next() {
13+
for i := 0; i < 100; i++ {
14+
buf.Write(s)
15+
}
16+
buf.Reset()
17+
}
18+
})
19+
}
20+
21+
func BenchmarkBytesBufferWrite(b *testing.B) {
22+
s := []byte("foobarbaz")
23+
b.RunParallel(func(pb *testing.PB) {
24+
var buf bytes.Buffer
25+
for pb.Next() {
26+
for i := 0; i < 100; i++ {
27+
buf.Write(s)
28+
}
29+
buf.Reset()
30+
}
31+
})
32+
}

doc.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Package bytebufferpool implements a pool of byte buffers
2+
// with anti-fragmentation protection.
3+
//
4+
// The pool may waste limited amount of memory due to fragmentation.
5+
// This amount equals to the maximum total size of the byte buffers
6+
// in concurrent use.
7+
package bytebufferpool

pool.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package bytebufferpool
2+
3+
import "sync"
4+
5+
const (
6+
minBitSize = 8
7+
steps = 20
8+
9+
minSize = 1 << minBitSize
10+
maxSize = 1 << (minBitSize + steps - 1)
11+
)
12+
13+
type byteBufferPool struct {
14+
// Pools are segemented into power-of-two sized buffers
15+
// from minSize bytes to maxSize.
16+
//
17+
// This allows reducing fragmentation of ByteBuffer objects.
18+
pools [steps]sync.Pool
19+
}
20+
21+
func (p *byteBufferPool) Acquire() *ByteBuffer {
22+
pools := &p.pools
23+
for i := 0; i < steps; i++ {
24+
v := pools[i].Get()
25+
if v != nil {
26+
return v.(*ByteBuffer)
27+
}
28+
}
29+
30+
return &ByteBuffer{
31+
B: make([]byte, 0, minSize),
32+
}
33+
}
34+
35+
func (p *byteBufferPool) Release(b *ByteBuffer) {
36+
n := cap(b.B)
37+
if n > maxSize {
38+
// Just drop oversized buffers.
39+
return
40+
}
41+
b.B = b.B[:0]
42+
idx := bitsize(n-1) >> minBitSize
43+
p.pools[idx].Put(b)
44+
}
45+
46+
func bitsize(n int) int {
47+
s := 0
48+
for n > 0 {
49+
n >>= 1
50+
s++
51+
}
52+
return s
53+
}

pool_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package bytebufferpool
2+
3+
import (
4+
"testing"
5+
"time"
6+
)
7+
8+
func TestPoolVariousSizesSerial(t *testing.T) {
9+
testPoolVariousSizes(t)
10+
}
11+
12+
func TestPoolVariousSizesConcurrent(t *testing.T) {
13+
concurrency := 5
14+
ch := make(chan struct{})
15+
for i := 0; i < concurrency; i++ {
16+
go func() {
17+
testPoolVariousSizes(t)
18+
ch <- struct{}{}
19+
}()
20+
}
21+
for i := 0; i < concurrency; i++ {
22+
select {
23+
case <-ch:
24+
case <-time.After(3 * time.Second):
25+
t.Fatalf("timeout")
26+
}
27+
}
28+
}
29+
30+
func testPoolVariousSizes(t *testing.T) {
31+
for i := 0; i < steps+1; i++ {
32+
n := (1 << uint32(i))
33+
34+
testAcquireRelease(t, n)
35+
testAcquireRelease(t, n+1)
36+
testAcquireRelease(t, n-1)
37+
38+
for j := 0; j < 10; j++ {
39+
testAcquireRelease(t, j+n)
40+
}
41+
}
42+
}
43+
44+
func testAcquireRelease(t *testing.T, n int) {
45+
bb := AcquireByteBuffer()
46+
if len(bb.B) > 0 {
47+
t.Fatalf("non-empty byte buffer returned from acquire")
48+
}
49+
bb.B = allocNBytes(bb.B, n)
50+
ReleaseByteBuffer(bb)
51+
}
52+
53+
func allocNBytes(dst []byte, n int) []byte {
54+
diff := n - cap(dst)
55+
if diff <= 0 {
56+
return dst[:n]
57+
}
58+
return append(dst, make([]byte, diff)...)
59+
}

0 commit comments

Comments
 (0)