pool.go 2.79 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.
//
tavit ohanian's avatar
tavit ohanian committed
4
//	import (pool "gitlab.dms3.io/p2p/go-buffer-pool")
5
//	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"
25
	"math/bits"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
26 27 28
	"sync"
)

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

// MaxLength is the maximum length of an element that can be added to the Pool.
33
const MaxLength = math.MaxInt32
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
34

35 36 37 38 39 40 41 42
// 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
43
	pools [32]sync.Pool // a list of singlePools
Steven Allen's avatar
Steven Allen committed
44 45 46 47 48
	ptrs  sync.Pool
}

type bufp struct {
	buf []byte
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
49 50
}

51 52 53 54
// 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
55
//
56
// If no suitable buffer exists in the pool, Get creates one.
Steven Allen's avatar
Steven Allen committed
57
func (p *BufferPool) Get(length int) []byte {
58 59
	if length == 0 {
		return nil
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
60
	}
Steven Allen's avatar
Steven Allen committed
61 62 63 64
	if length > MaxLength {
		return make([]byte, length)
	}
	idx := nextLogBase2(uint32(length))
Steven Allen's avatar
Steven Allen committed
65 66 67 68 69 70
	if ptr := p.pools[idx].Get(); ptr != nil {
		bp := ptr.(*bufp)
		buf := bp.buf[:uint32(length)]
		bp.buf = nil
		p.ptrs.Put(ptr)
		return buf
71
	}
Steven Allen's avatar
Steven Allen committed
72
	return make([]byte, 1<<idx)[:uint32(length)]
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
73 74 75
}

// Put adds x to the pool.
76 77 78 79 80 81
func (p *BufferPool) Put(buf []byte) {
	capacity := cap(buf)
	if capacity == 0 || capacity > MaxLength {
		return // drop it
	}
	idx := prevLogBase2(uint32(capacity))
Steven Allen's avatar
Steven Allen committed
82 83 84 85 86 87 88 89
	var bp *bufp
	if ptr := p.ptrs.Get(); ptr != nil {
		bp = ptr.(*bufp)
	} else {
		bp = new(bufp)
	}
	bp.buf = buf
	p.pools[idx].Put(bp)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
90 91
}

92 93
// 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
94
func Get(length int) []byte {
95 96
	return GlobalPool.Get(length)
}
97

98 99 100 101
// 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
102

103 104 105
// 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
106 107
}

108 109 110 111 112
// 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
113
	}
114
	return next - 1
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
115
}