mirror of
https://github.com/valyala/bytebufferpool.git
synced 2026-06-14 13:26:35 +03:00
Initial implementation
This commit is contained in:
+15
@@ -0,0 +1,15 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.6
|
||||||
|
|
||||||
|
script:
|
||||||
|
# build test for supported platforms
|
||||||
|
- GOOS=linux go build
|
||||||
|
- GOOS=darwin go build
|
||||||
|
- GOOS=freebsd go build
|
||||||
|
- GOOS=windows go build
|
||||||
|
- GOARCH=386 go build
|
||||||
|
|
||||||
|
# run tests on a standard platform
|
||||||
|
- go test -v ./...
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 Aliaksandr Valialkin, VertaMedia
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
[](https://travis-ci.org/valyala/bytebufferpool)
|
||||||
|
[](http://godoc.org/github.com/valyala/bytebufferpool)
|
||||||
|
[](http://goreportcard.com/report/valyala/bytebufferpool)
|
||||||
|
|
||||||
|
# bytebufferpool
|
||||||
|
|
||||||
|
An implementation of a pool of byte buffers with anti-fragmentation protection.
|
||||||
|
|
||||||
|
The pool may waste limited amount of memory due to fragmentation.
|
||||||
|
This amount equals to the maximum total size of the byte buffers
|
||||||
|
in concurrent use.
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package bytebufferpool
|
||||||
|
|
||||||
|
// ByteBuffer provides byte buffer, which can be used for minimizing
|
||||||
|
// memory allocations.
|
||||||
|
//
|
||||||
|
// ByteBuffer may be used with functions appending data to the given []byte
|
||||||
|
// slice. See example code for details.
|
||||||
|
//
|
||||||
|
// Use AcquireByteBuffer for obtaining an empty byte buffer.
|
||||||
|
type ByteBuffer struct {
|
||||||
|
|
||||||
|
// B is a byte buffer to use in append-like workloads.
|
||||||
|
// See example code for details.
|
||||||
|
B []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements io.Writer - it appends p to ByteBuffer.B
|
||||||
|
func (b *ByteBuffer) Write(p []byte) (int, error) {
|
||||||
|
b.B = append(b.B, p...)
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteString appends s to ByteBuffer.B
|
||||||
|
func (b *ByteBuffer) WriteString(s string) (int, error) {
|
||||||
|
b.B = append(b.B, s...)
|
||||||
|
return len(s), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets ByteBuffer.B to p
|
||||||
|
func (b *ByteBuffer) Set(p []byte) {
|
||||||
|
b.B = append(b.B[:0], p...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetString sets ByteBuffer.B to s
|
||||||
|
func (b *ByteBuffer) SetString(s string) {
|
||||||
|
b.B = append(b.B[:0], s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset makes ByteBuffer.B empty.
|
||||||
|
func (b *ByteBuffer) Reset() {
|
||||||
|
b.B = b.B[:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcquireByteBuffer returns an empty byte buffer from the pool.
|
||||||
|
//
|
||||||
|
// Acquired byte buffer may be returned to the pool via ReleaseByteBuffer call.
|
||||||
|
// This reduces the number of memory allocations required for byte buffer
|
||||||
|
// management.
|
||||||
|
func AcquireByteBuffer() *ByteBuffer {
|
||||||
|
return defaultByteBufferPool.Acquire()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReleaseByteBuffer returns byte buffer to the pool.
|
||||||
|
//
|
||||||
|
// ByteBuffer.B mustn't be touched after returning it to the pool.
|
||||||
|
// Otherwise data races will occur.
|
||||||
|
func ReleaseByteBuffer(b *ByteBuffer) {
|
||||||
|
defaultByteBufferPool.Release(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultByteBufferPool byteBufferPool
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package bytebufferpool_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/valyala/bytebufferpool"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleByteBuffer() {
|
||||||
|
bb := bytebufferpool.AcquireByteBuffer()
|
||||||
|
|
||||||
|
bb.WriteString("first line\n")
|
||||||
|
bb.Write([]byte("second line\n"))
|
||||||
|
bb.B = append(bb.B, "third line\n"...)
|
||||||
|
|
||||||
|
fmt.Printf("bytebuffer contents=%q", bb.B)
|
||||||
|
|
||||||
|
// It is safe to release byte buffer now, since it is
|
||||||
|
// no longer used.
|
||||||
|
bytebufferpool.ReleaseByteBuffer(bb)
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package bytebufferpool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestByteBufferAcquireReleaseSerial(t *testing.T) {
|
||||||
|
testByteBufferAcquireRelease(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestByteBufferAcquireReleaseConcurrent(t *testing.T) {
|
||||||
|
concurrency := 10
|
||||||
|
ch := make(chan struct{}, concurrency)
|
||||||
|
for i := 0; i < concurrency; i++ {
|
||||||
|
go func() {
|
||||||
|
testByteBufferAcquireRelease(t)
|
||||||
|
ch <- struct{}{}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < concurrency; i++ {
|
||||||
|
select {
|
||||||
|
case <-ch:
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatalf("timeout!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testByteBufferAcquireRelease(t *testing.T) {
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
expectedS := fmt.Sprintf("num %d", i)
|
||||||
|
b := AcquireByteBuffer()
|
||||||
|
b.B = append(b.B, "num "...)
|
||||||
|
b.B = append(b.B, fmt.Sprintf("%d", i)...)
|
||||||
|
if string(b.B) != expectedS {
|
||||||
|
t.Fatalf("unexpected result: %q. Expecting %q", b.B, expectedS)
|
||||||
|
}
|
||||||
|
ReleaseByteBuffer(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package bytebufferpool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkByteBufferWrite(b *testing.B) {
|
||||||
|
s := []byte("foobarbaz")
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
var buf ByteBuffer
|
||||||
|
for pb.Next() {
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
buf.Write(s)
|
||||||
|
}
|
||||||
|
buf.Reset()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkBytesBufferWrite(b *testing.B) {
|
||||||
|
s := []byte("foobarbaz")
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for pb.Next() {
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
buf.Write(s)
|
||||||
|
}
|
||||||
|
buf.Reset()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// Package bytebufferpool implements a pool of byte buffers
|
||||||
|
// with anti-fragmentation protection.
|
||||||
|
//
|
||||||
|
// The pool may waste limited amount of memory due to fragmentation.
|
||||||
|
// This amount equals to the maximum total size of the byte buffers
|
||||||
|
// in concurrent use.
|
||||||
|
package bytebufferpool
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package bytebufferpool
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
const (
|
||||||
|
minBitSize = 8
|
||||||
|
steps = 20
|
||||||
|
|
||||||
|
minSize = 1 << minBitSize
|
||||||
|
maxSize = 1 << (minBitSize + steps - 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
type byteBufferPool struct {
|
||||||
|
// Pools are segemented into power-of-two sized buffers
|
||||||
|
// from minSize bytes to maxSize.
|
||||||
|
//
|
||||||
|
// This allows reducing fragmentation of ByteBuffer objects.
|
||||||
|
pools [steps]sync.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *byteBufferPool) Acquire() *ByteBuffer {
|
||||||
|
pools := &p.pools
|
||||||
|
for i := 0; i < steps; i++ {
|
||||||
|
v := pools[i].Get()
|
||||||
|
if v != nil {
|
||||||
|
return v.(*ByteBuffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ByteBuffer{
|
||||||
|
B: make([]byte, 0, minSize),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *byteBufferPool) Release(b *ByteBuffer) {
|
||||||
|
n := cap(b.B)
|
||||||
|
if n > maxSize {
|
||||||
|
// Just drop oversized buffers.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.B = b.B[:0]
|
||||||
|
idx := bitsize(n-1) >> minBitSize
|
||||||
|
p.pools[idx].Put(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func bitsize(n int) int {
|
||||||
|
s := 0
|
||||||
|
for n > 0 {
|
||||||
|
n >>= 1
|
||||||
|
s++
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package bytebufferpool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPoolVariousSizesSerial(t *testing.T) {
|
||||||
|
testPoolVariousSizes(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPoolVariousSizesConcurrent(t *testing.T) {
|
||||||
|
concurrency := 5
|
||||||
|
ch := make(chan struct{})
|
||||||
|
for i := 0; i < concurrency; i++ {
|
||||||
|
go func() {
|
||||||
|
testPoolVariousSizes(t)
|
||||||
|
ch <- struct{}{}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
for i := 0; i < concurrency; i++ {
|
||||||
|
select {
|
||||||
|
case <-ch:
|
||||||
|
case <-time.After(3 * time.Second):
|
||||||
|
t.Fatalf("timeout")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPoolVariousSizes(t *testing.T) {
|
||||||
|
for i := 0; i < steps+1; i++ {
|
||||||
|
n := (1 << uint32(i))
|
||||||
|
|
||||||
|
testAcquireRelease(t, n)
|
||||||
|
testAcquireRelease(t, n+1)
|
||||||
|
testAcquireRelease(t, n-1)
|
||||||
|
|
||||||
|
for j := 0; j < 10; j++ {
|
||||||
|
testAcquireRelease(t, j+n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAcquireRelease(t *testing.T, n int) {
|
||||||
|
bb := AcquireByteBuffer()
|
||||||
|
if len(bb.B) > 0 {
|
||||||
|
t.Fatalf("non-empty byte buffer returned from acquire")
|
||||||
|
}
|
||||||
|
bb.B = allocNBytes(bb.B, n)
|
||||||
|
ReleaseByteBuffer(bb)
|
||||||
|
}
|
||||||
|
|
||||||
|
func allocNBytes(dst []byte, n int) []byte {
|
||||||
|
diff := n - cap(dst)
|
||||||
|
if diff <= 0 {
|
||||||
|
return dst[:n]
|
||||||
|
}
|
||||||
|
return append(dst, make([]byte, diff)...)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user