├── go.mod ├── doc.go ├── bytebuffer_example_test.go ├── .travis.yml ├── bytebuffer_timing_test.go ├── README.md ├── LICENSE ├── pool_test.go ├── bytebuffer.go ├── bytebuffer_test.go └── pool.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/valyala/bytebufferpool 2 | 3 | go 1.12 4 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 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 8 | -------------------------------------------------------------------------------- /bytebuffer_example_test.go: -------------------------------------------------------------------------------- 1 | package bytebufferpool_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/valyala/bytebufferpool" 7 | ) 8 | 9 | func ExampleByteBuffer() { 10 | bb := bytebufferpool.Get() 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.Put(bb) 21 | } 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | arch: 3 | - amd64 4 | - ppc64le 5 | os: 6 | - linux 7 | - osx 8 | - windows 9 | 10 | go: 11 | - 1.15.x 12 | - 1.14.x 13 | - 1.13.x 14 | - 1.12.x 15 | - tip 16 | 17 | script: 18 | - go test -v -cover -race ./... 19 | 20 | jobs: 21 | allow_failures: 22 | - go: tip 23 | 24 | include: 25 | - stage: cross compilation 26 | script: 27 | - GOOS=linux go build 28 | - GOOS=darwin go build 29 | - GOOS=freebsd go build 30 | - GOOS=windows go build 31 | - GOARCH=386 go build 32 | -------------------------------------------------------------------------------- /bytebuffer_timing_test.go: -------------------------------------------------------------------------------- 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 | } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 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-memory-waste 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. 12 | 13 | # Benchmark results 14 | Currently bytebufferpool is fastest and most effective buffer pool written in Go. 15 | 16 | You can find results [here](https://omgnull.github.io/go-benchmark/buffer/). 17 | 18 | # bytebufferpool users 19 | 20 | * [fasthttp](https://github.com/valyala/fasthttp) 21 | * [quicktemplate](https://github.com/valyala/quicktemplate) 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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 | 23 | -------------------------------------------------------------------------------- /pool_test.go: -------------------------------------------------------------------------------- 1 | package bytebufferpool 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestIndex(t *testing.T) { 10 | testIndex(t, 0, 0) 11 | testIndex(t, 1, 0) 12 | 13 | testIndex(t, minSize-1, 0) 14 | testIndex(t, minSize, 0) 15 | testIndex(t, minSize+1, 1) 16 | 17 | testIndex(t, 2*minSize-1, 1) 18 | testIndex(t, 2*minSize, 1) 19 | testIndex(t, 2*minSize+1, 2) 20 | 21 | testIndex(t, maxSize-1, steps-1) 22 | testIndex(t, maxSize, steps-1) 23 | testIndex(t, maxSize+1, steps-1) 24 | } 25 | 26 | func testIndex(t *testing.T, n, expectedIdx int) { 27 | idx := index(n) 28 | if idx != expectedIdx { 29 | t.Fatalf("unexpected idx for n=%d: %d. Expecting %d", n, idx, expectedIdx) 30 | } 31 | } 32 | 33 | func TestPoolCalibrate(t *testing.T) { 34 | for i := 0; i < steps*calibrateCallsThreshold; i++ { 35 | n := 1004 36 | if i%15 == 0 { 37 | n = rand.Intn(15234) 38 | } 39 | testGetPut(t, n) 40 | } 41 | } 42 | 43 | func TestPoolVariousSizesSerial(t *testing.T) { 44 | testPoolVariousSizes(t) 45 | } 46 | 47 | func TestPoolVariousSizesConcurrent(t *testing.T) { 48 | concurrency := 5 49 | ch := make(chan struct{}) 50 | for i := 0; i < concurrency; i++ { 51 | go func() { 52 | testPoolVariousSizes(t) 53 | ch <- struct{}{} 54 | }() 55 | } 56 | for i := 0; i < concurrency; i++ { 57 | select { 58 | case <-ch: 59 | case <-time.After(3 * time.Second): 60 | t.Fatalf("timeout") 61 | } 62 | } 63 | } 64 | 65 | func testPoolVariousSizes(t *testing.T) { 66 | for i := 0; i < steps+1; i++ { 67 | n := (1 << uint32(i)) 68 | 69 | testGetPut(t, n) 70 | testGetPut(t, n+1) 71 | testGetPut(t, n-1) 72 | 73 | for j := 0; j < 10; j++ { 74 | testGetPut(t, j+n) 75 | } 76 | } 77 | } 78 | 79 | func testGetPut(t *testing.T, n int) { 80 | bb := Get() 81 | if len(bb.B) > 0 { 82 | t.Fatalf("non-empty byte buffer returned from acquire") 83 | } 84 | bb.B = allocNBytes(bb.B, n) 85 | Put(bb) 86 | } 87 | 88 | func allocNBytes(dst []byte, n int) []byte { 89 | diff := n - cap(dst) 90 | if diff <= 0 { 91 | return dst[:n] 92 | } 93 | return append(dst, make([]byte, diff)...) 94 | } 95 | -------------------------------------------------------------------------------- /bytebuffer.go: -------------------------------------------------------------------------------- 1 | package bytebufferpool 2 | 3 | import "io" 4 | 5 | // ByteBuffer provides byte buffer, which can be used for minimizing 6 | // memory allocations. 7 | // 8 | // ByteBuffer may be used with functions appending data to the given []byte 9 | // slice. See example code for details. 10 | // 11 | // Use Get for obtaining an empty byte buffer. 12 | type ByteBuffer struct { 13 | 14 | // B is a byte buffer to use in append-like workloads. 15 | // See example code for details. 16 | B []byte 17 | } 18 | 19 | // Len returns the size of the byte buffer. 20 | func (b *ByteBuffer) Len() int { 21 | return len(b.B) 22 | } 23 | 24 | // ReadFrom implements io.ReaderFrom. 25 | // 26 | // The function appends all the data read from r to b. 27 | func (b *ByteBuffer) ReadFrom(r io.Reader) (int64, error) { 28 | p := b.B 29 | nStart := int64(len(p)) 30 | nMax := int64(cap(p)) 31 | n := nStart 32 | if nMax == 0 { 33 | nMax = 64 34 | p = make([]byte, nMax) 35 | } else { 36 | p = p[:nMax] 37 | } 38 | for { 39 | if n == nMax { 40 | nMax *= 2 41 | bNew := make([]byte, nMax) 42 | copy(bNew, p) 43 | p = bNew 44 | } 45 | nn, err := r.Read(p[n:]) 46 | n += int64(nn) 47 | if err != nil { 48 | b.B = p[:n] 49 | n -= nStart 50 | if err == io.EOF { 51 | return n, nil 52 | } 53 | return n, err 54 | } 55 | } 56 | } 57 | 58 | // WriteTo implements io.WriterTo. 59 | func (b *ByteBuffer) WriteTo(w io.Writer) (int64, error) { 60 | n, err := w.Write(b.B) 61 | return int64(n), err 62 | } 63 | 64 | // Bytes returns b.B, i.e. all the bytes accumulated in the buffer. 65 | // 66 | // The purpose of this function is bytes.Buffer compatibility. 67 | func (b *ByteBuffer) Bytes() []byte { 68 | return b.B 69 | } 70 | 71 | // Write implements io.Writer - it appends p to ByteBuffer.B 72 | func (b *ByteBuffer) Write(p []byte) (int, error) { 73 | b.B = append(b.B, p...) 74 | return len(p), nil 75 | } 76 | 77 | // WriteByte appends the byte c to the buffer. 78 | // 79 | // The purpose of this function is bytes.Buffer compatibility. 80 | // 81 | // The function always returns nil. 82 | func (b *ByteBuffer) WriteByte(c byte) error { 83 | b.B = append(b.B, c) 84 | return nil 85 | } 86 | 87 | // WriteString appends s to ByteBuffer.B. 88 | func (b *ByteBuffer) WriteString(s string) (int, error) { 89 | b.B = append(b.B, s...) 90 | return len(s), nil 91 | } 92 | 93 | // Set sets ByteBuffer.B to p. 94 | func (b *ByteBuffer) Set(p []byte) { 95 | b.B = append(b.B[:0], p...) 96 | } 97 | 98 | // SetString sets ByteBuffer.B to s. 99 | func (b *ByteBuffer) SetString(s string) { 100 | b.B = append(b.B[:0], s...) 101 | } 102 | 103 | // String returns string representation of ByteBuffer.B. 104 | func (b *ByteBuffer) String() string { 105 | return string(b.B) 106 | } 107 | 108 | // Reset makes ByteBuffer.B empty. 109 | func (b *ByteBuffer) Reset() { 110 | b.B = b.B[:0] 111 | } 112 | -------------------------------------------------------------------------------- /bytebuffer_test.go: -------------------------------------------------------------------------------- 1 | package bytebufferpool 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestByteBufferReadFrom(t *testing.T) { 12 | prefix := "foobar" 13 | expectedS := "asadfsdafsadfasdfisdsdfa" 14 | prefixLen := int64(len(prefix)) 15 | expectedN := int64(len(expectedS)) 16 | 17 | var bb ByteBuffer 18 | bb.WriteString(prefix) 19 | 20 | rf := (io.ReaderFrom)(&bb) 21 | for i := 0; i < 20; i++ { 22 | r := bytes.NewBufferString(expectedS) 23 | n, err := rf.ReadFrom(r) 24 | if n != expectedN { 25 | t.Fatalf("unexpected n=%d. Expecting %d. iteration %d", n, expectedN, i) 26 | } 27 | if err != nil { 28 | t.Fatalf("unexpected error: %s", err) 29 | } 30 | bbLen := int64(bb.Len()) 31 | expectedLen := prefixLen + int64(i+1)*expectedN 32 | if bbLen != expectedLen { 33 | t.Fatalf("unexpected byteBuffer length: %d. Expecting %d", bbLen, expectedLen) 34 | } 35 | for j := 0; j < i; j++ { 36 | start := prefixLen + int64(j)*expectedN 37 | b := bb.B[start : start+expectedN] 38 | if string(b) != expectedS { 39 | t.Fatalf("unexpected byteBuffer contents: %q. Expecting %q", b, expectedS) 40 | } 41 | } 42 | } 43 | } 44 | 45 | func TestByteBufferWriteTo(t *testing.T) { 46 | expectedS := "foobarbaz" 47 | var bb ByteBuffer 48 | bb.WriteString(expectedS[:3]) 49 | bb.WriteString(expectedS[3:]) 50 | 51 | wt := (io.WriterTo)(&bb) 52 | var w bytes.Buffer 53 | for i := 0; i < 10; i++ { 54 | n, err := wt.WriteTo(&w) 55 | if n != int64(len(expectedS)) { 56 | t.Fatalf("unexpected n returned from WriteTo: %d. Expecting %d", n, len(expectedS)) 57 | } 58 | if err != nil { 59 | t.Fatalf("unexpected error: %s", err) 60 | } 61 | s := string(w.Bytes()) 62 | if s != expectedS { 63 | t.Fatalf("unexpected string written %q. Expecting %q", s, expectedS) 64 | } 65 | w.Reset() 66 | } 67 | } 68 | 69 | func TestByteBufferGetPutSerial(t *testing.T) { 70 | testByteBufferGetPut(t) 71 | } 72 | 73 | func TestByteBufferGetPutConcurrent(t *testing.T) { 74 | concurrency := 10 75 | ch := make(chan struct{}, concurrency) 76 | for i := 0; i < concurrency; i++ { 77 | go func() { 78 | testByteBufferGetPut(t) 79 | ch <- struct{}{} 80 | }() 81 | } 82 | 83 | for i := 0; i < concurrency; i++ { 84 | select { 85 | case <-ch: 86 | case <-time.After(time.Second): 87 | t.Fatalf("timeout!") 88 | } 89 | } 90 | } 91 | 92 | func testByteBufferGetPut(t *testing.T) { 93 | for i := 0; i < 10; i++ { 94 | expectedS := fmt.Sprintf("num %d", i) 95 | b := Get() 96 | b.B = append(b.B, "num "...) 97 | b.B = append(b.B, fmt.Sprintf("%d", i)...) 98 | if string(b.B) != expectedS { 99 | t.Fatalf("unexpected result: %q. Expecting %q", b.B, expectedS) 100 | } 101 | Put(b) 102 | } 103 | } 104 | 105 | func testByteBufferGetString(t *testing.T) { 106 | for i := 0; i < 10; i++ { 107 | expectedS := fmt.Sprintf("num %d", i) 108 | b := Get() 109 | b.SetString(expectedS) 110 | if b.String() != expectedS { 111 | t.Fatalf("unexpected result: %q. Expecting %q", b.B, expectedS) 112 | } 113 | Put(b) 114 | } 115 | } 116 | 117 | func TestByteBufferGetStringSerial(t *testing.T) { 118 | testByteBufferGetString(t) 119 | } 120 | 121 | func TestByteBufferGetStringConcurrent(t *testing.T) { 122 | concurrency := 10 123 | ch := make(chan struct{}, concurrency) 124 | for i := 0; i < concurrency; i++ { 125 | go func() { 126 | testByteBufferGetString(t) 127 | ch <- struct{}{} 128 | }() 129 | } 130 | 131 | for i := 0; i < concurrency; i++ { 132 | select { 133 | case <-ch: 134 | case <-time.After(time.Second): 135 | t.Fatalf("timeout!") 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /pool.go: -------------------------------------------------------------------------------- 1 | package bytebufferpool 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | "sync/atomic" 7 | ) 8 | 9 | const ( 10 | minBitSize = 6 // 2**6=64 is a CPU cache line size 11 | steps = 20 12 | 13 | minSize = 1 << minBitSize 14 | maxSize = 1 << (minBitSize + steps - 1) 15 | 16 | calibrateCallsThreshold = 42000 17 | maxPercentile = 0.95 18 | ) 19 | 20 | // Pool represents byte buffer pool. 21 | // 22 | // Distinct pools may be used for distinct types of byte buffers. 23 | // Properly determined byte buffer types with their own pools may help reducing 24 | // memory waste. 25 | type Pool struct { 26 | calls [steps]uint64 27 | calibrating uint64 28 | 29 | defaultSize uint64 30 | maxSize uint64 31 | 32 | pool sync.Pool 33 | } 34 | 35 | var defaultPool Pool 36 | 37 | // Get returns an empty byte buffer from the pool. 38 | // 39 | // Got byte buffer may be returned to the pool via Put call. 40 | // This reduces the number of memory allocations required for byte buffer 41 | // management. 42 | func Get() *ByteBuffer { return defaultPool.Get() } 43 | 44 | // Get returns new byte buffer with zero length. 45 | // 46 | // The byte buffer may be returned to the pool via Put after the use 47 | // in order to minimize GC overhead. 48 | func (p *Pool) Get() *ByteBuffer { 49 | v := p.pool.Get() 50 | if v != nil { 51 | return v.(*ByteBuffer) 52 | } 53 | return &ByteBuffer{ 54 | B: make([]byte, 0, atomic.LoadUint64(&p.defaultSize)), 55 | } 56 | } 57 | 58 | // Put returns byte buffer to the pool. 59 | // 60 | // ByteBuffer.B mustn't be touched after returning it to the pool. 61 | // Otherwise data races will occur. 62 | func Put(b *ByteBuffer) { defaultPool.Put(b) } 63 | 64 | // Put releases byte buffer obtained via Get to the pool. 65 | // 66 | // The buffer mustn't be accessed after returning to the pool. 67 | func (p *Pool) Put(b *ByteBuffer) { 68 | idx := index(len(b.B)) 69 | 70 | if atomic.AddUint64(&p.calls[idx], 1) > calibrateCallsThreshold { 71 | p.calibrate() 72 | } 73 | 74 | maxSize := int(atomic.LoadUint64(&p.maxSize)) 75 | if maxSize == 0 || cap(b.B) <= maxSize { 76 | b.Reset() 77 | p.pool.Put(b) 78 | } 79 | } 80 | 81 | func (p *Pool) calibrate() { 82 | if !atomic.CompareAndSwapUint64(&p.calibrating, 0, 1) { 83 | return 84 | } 85 | 86 | a := make(callSizes, 0, steps) 87 | var callsSum uint64 88 | for i := uint64(0); i < steps; i++ { 89 | calls := atomic.SwapUint64(&p.calls[i], 0) 90 | callsSum += calls 91 | a = append(a, callSize{ 92 | calls: calls, 93 | size: minSize << i, 94 | }) 95 | } 96 | sort.Sort(a) 97 | 98 | defaultSize := a[0].size 99 | maxSize := defaultSize 100 | 101 | maxSum := uint64(float64(callsSum) * maxPercentile) 102 | callsSum = 0 103 | for i := 0; i < steps; i++ { 104 | if callsSum > maxSum { 105 | break 106 | } 107 | callsSum += a[i].calls 108 | size := a[i].size 109 | if size > maxSize { 110 | maxSize = size 111 | } 112 | } 113 | 114 | atomic.StoreUint64(&p.defaultSize, defaultSize) 115 | atomic.StoreUint64(&p.maxSize, maxSize) 116 | 117 | atomic.StoreUint64(&p.calibrating, 0) 118 | } 119 | 120 | type callSize struct { 121 | calls uint64 122 | size uint64 123 | } 124 | 125 | type callSizes []callSize 126 | 127 | func (ci callSizes) Len() int { 128 | return len(ci) 129 | } 130 | 131 | func (ci callSizes) Less(i, j int) bool { 132 | return ci[i].calls > ci[j].calls 133 | } 134 | 135 | func (ci callSizes) Swap(i, j int) { 136 | ci[i], ci[j] = ci[j], ci[i] 137 | } 138 | 139 | func index(n int) int { 140 | n-- 141 | n >>= minBitSize 142 | idx := 0 143 | for n > 0 { 144 | n >>= 1 145 | idx++ 146 | } 147 | if idx >= steps { 148 | idx = steps - 1 149 | } 150 | return idx 151 | } 152 | --------------------------------------------------------------------------------