bufferpool.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. // Copyright (C) 2016 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package protocol
  7. import (
  8. "fmt"
  9. "sync"
  10. "sync/atomic"
  11. )
  12. // Global pool to get buffers from. Initialized in init().
  13. var BufferPool *bufferPool
  14. type bufferPool struct {
  15. puts atomic.Int64
  16. skips atomic.Int64
  17. misses atomic.Int64
  18. pools []sync.Pool
  19. hits []atomic.Int64
  20. }
  21. func newBufferPool() *bufferPool {
  22. return &bufferPool{
  23. pools: make([]sync.Pool, len(BlockSizes)),
  24. hits: make([]atomic.Int64, len(BlockSizes)),
  25. }
  26. }
  27. func (p *bufferPool) Get(size int) []byte {
  28. // Too big, isn't pooled
  29. if size > MaxBlockSize {
  30. p.skips.Add(1)
  31. return make([]byte, size)
  32. }
  33. // Try the fitting and all bigger pools
  34. bkt := getBucketForLen(size)
  35. for j := bkt; j < len(BlockSizes); j++ {
  36. if intf := p.pools[j].Get(); intf != nil {
  37. p.hits[j].Add(1)
  38. bs := *intf.(*[]byte)
  39. return bs[:size]
  40. }
  41. }
  42. p.misses.Add(1)
  43. // All pools are empty, must allocate. For very small slices where we
  44. // didn't have a block to reuse, just allocate a small slice instead of
  45. // a large one. We won't be able to reuse it, but avoid some overhead.
  46. if size < MinBlockSize/64 {
  47. return make([]byte, size)
  48. }
  49. return make([]byte, BlockSizes[bkt])[:size]
  50. }
  51. // Put makes the given byte slice available again in the global pool.
  52. // You must only Put() slices that were returned by Get().
  53. func (p *bufferPool) Put(bs []byte) {
  54. // Don't buffer slices outside of our pool range
  55. if cap(bs) > MaxBlockSize || cap(bs) < MinBlockSize {
  56. p.skips.Add(1)
  57. return
  58. }
  59. p.puts.Add(1)
  60. bkt := putBucketForCap(cap(bs))
  61. p.pools[bkt].Put(&bs)
  62. }
  63. // getBucketForLen returns the bucket where we should get a slice of a
  64. // certain length. Each bucket is guaranteed to hold slices that are
  65. // precisely the block size for that bucket, so if the block size is larger
  66. // than our size we are good.
  67. func getBucketForLen(len int) int {
  68. for i, blockSize := range BlockSizes {
  69. if len <= blockSize {
  70. return i
  71. }
  72. }
  73. panic(fmt.Sprintf("bug: tried to get impossible block len %d", len))
  74. }
  75. // putBucketForCap returns the bucket where we should put a slice of a
  76. // certain capacity. Each bucket is guaranteed to hold slices that are
  77. // precisely the block size for that bucket, so we just find the matching
  78. // one.
  79. func putBucketForCap(cap int) int {
  80. for i, blockSize := range BlockSizes {
  81. if cap == blockSize {
  82. return i
  83. }
  84. }
  85. panic(fmt.Sprintf("bug: tried to put impossible block cap %d", cap))
  86. }