pool.go 2.56 KB
Newer Older
1
// Package pool provides a sync.Pool equivalent that buckets incoming
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
2 3
// requests to one of 32 sub-pools, one for each power of 2, 0-32.
//
4 5
//	import (pool "github.com/libp2p/go-buffer-pool")
//	var p pool.BufferPool
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
6 7 8
//
//	small := make([]byte, 1024)
//	large := make([]byte, 4194304)
9 10
//	p.Put(small)
//	p.Put(large)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
11
//
12 13
//	small2 := p.Get(1024)
//	large2 := p.Get(4194304)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
14 15 16 17 18 19 20
//	fmt.Println("small2 len:", len(small2))
//	fmt.Println("large2 len:", len(large2))
//
//	// Output:
//	// small2 len: 1024
//	// large2 len: 4194304
//
21
package pool
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
22 23

import (
24
	"math/bits"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
25 26 27
	"sync"
)

28 29
// GlobalPool is a static Pool for reusing byteslices of various sizes.
var GlobalPool = new(BufferPool)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
30 31 32 33

// MaxLength is the maximum length of an element that can be added to the Pool.
const MaxLength = 1 << 32

34 35 36 37 38 39 40 41
// BufferPool is a pool to handle cases of reusing elements of varying sizes. It
// maintains 32 internal pools, for each power of 2 in 0-32.
//
// You should generally just call the package level Get and Put methods or use
// the GlobalPool BufferPool instead of constructing your own.
//
// You MUST NOT copy Pool after using.
type BufferPool struct {
Steven Allen's avatar
Steven Allen committed
42
	pools [32]sync.Pool // a list of singlePools
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
43 44
}

45 46 47 48
// Get retrieves a buffer of the appropriate length from the buffer pool or
// allocates a new one. Get may choose to ignore the pool and treat it as empty.
// Callers should not assume any relation between values passed to Put and the
// values returned by Get.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
49
//
50
// If no suitable buffer exists in the pool, Get creates one.
Steven Allen's avatar
Steven Allen committed
51
func (p *BufferPool) Get(length int) []byte {
52 53
	if length == 0 {
		return nil
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
54
	}
Steven Allen's avatar
Steven Allen committed
55 56 57 58
	if length > MaxLength {
		return make([]byte, length)
	}
	idx := nextLogBase2(uint32(length))
59
	if buf := p.pools[idx].Get(); buf != nil {
Steven Allen's avatar
Steven Allen committed
60
		return buf.([]byte)[:uint32(length)]
61
	}
Steven Allen's avatar
Steven Allen committed
62
	return make([]byte, 1<<idx)[:uint32(length)]
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
63 64 65
}

// Put adds x to the pool.
66 67 68 69 70 71 72
func (p *BufferPool) Put(buf []byte) {
	capacity := cap(buf)
	if capacity == 0 || capacity > MaxLength {
		return // drop it
	}
	idx := prevLogBase2(uint32(capacity))
	p.pools[idx].Put(buf)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
73 74
}

75 76
// Get retrieves a buffer of the appropriate length from the global buffer pool
// (or allocates a new one).
Steven Allen's avatar
Steven Allen committed
77
func Get(length int) []byte {
78 79
	return GlobalPool.Get(length)
}
80

81 82 83 84
// Put returns a buffer to the global buffer pool.
func Put(slice []byte) {
	GlobalPool.Put(slice)
}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
85

86 87 88
// Log of base two, round up (for v > 0).
func nextLogBase2(v uint32) uint32 {
	return uint32(bits.Len32(v - 1))
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
89 90
}

91 92 93 94 95
// Log of base two, round down (for v > 0)
func prevLogBase2(num uint32) uint32 {
	next := nextLogBase2(num)
	if num == (1 << uint32(next)) {
		return next
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
96
	}
97
	return next - 1
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
98
}