pool.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. package memory
  2. import (
  3. "github.com/pkg/errors"
  4. )
  5. const (
  6. minimumClassSize = MiB
  7. maximumClassSize = 4 * GiB
  8. memoryClassNumber = 7
  9. )
  10. var (
  11. ErrInvalidMemoryClass = errors.New("invalid memory class")
  12. ErrEarlyMerge = errors.New("not all children have been freed")
  13. ErrEmptyPoolOperation = errors.New("operation on empty pool")
  14. )
  15. // GetMemoryClassType returns the minimum memory class type that can hold a device of
  16. // a given size. The smallest class is 1MB and the largest one is 4GB with 2 bit offset
  17. // intervals in between, for a total of 7 different classes. This function does not
  18. // do a validity check
  19. func GetMemoryClassType(s uint64) classType {
  20. s = (s - 1) >> 20
  21. memCls := uint32(0)
  22. for s > 0 {
  23. s = s >> 2
  24. memCls++
  25. }
  26. return classType(memCls)
  27. }
  28. // GetMemoryClassSize returns size in bytes for a given memory class
  29. func GetMemoryClassSize(memCls classType) (uint64, error) {
  30. if memCls >= memoryClassNumber {
  31. return 0, ErrInvalidMemoryClass
  32. }
  33. return minimumClassSize << (2 * memCls), nil
  34. }
  35. // region represents a contiguous memory block
  36. type region struct {
  37. // parent region that has been split into 4
  38. parent *region
  39. class classType
  40. // offset represents offset in bytes
  41. offset uint64
  42. }
  43. // memoryPool tracks free and busy (used) memory regions
  44. type memoryPool struct {
  45. free map[uint64]*region
  46. busy map[uint64]*region
  47. }
  48. // PoolAllocator implements a memory allocation strategy similar to buddy-malloc https://github.com/evanw/buddy-malloc/blob/master/buddy-malloc.c
  49. // We borrow the idea of spanning a tree of fixed size regions on top of a contiguous memory
  50. // space.
  51. //
  52. // There are a total of 7 different region sizes that can be allocated, with the smallest
  53. // being 1MB and the largest 4GB (the default maximum size of a Virtual PMem device).
  54. //
  55. // For efficiency and to reduce fragmentation an entire region is allocated when requested.
  56. // When there's no available region of requested size, we try to allocate more memory for
  57. // this particular size by splitting the next available larger region into smaller ones, e.g.
  58. // if there's no region available for size class 0, we try splitting a region from class 1,
  59. // then class 2 etc, until we are able to do so or hit the upper limit.
  60. type PoolAllocator struct {
  61. pools [memoryClassNumber]*memoryPool
  62. }
  63. var _ MappedRegion = &region{}
  64. var _ Allocator = &PoolAllocator{}
  65. func (r *region) Offset() uint64 {
  66. return r.offset
  67. }
  68. func (r *region) Size() uint64 {
  69. sz, err := GetMemoryClassSize(r.class)
  70. if err != nil {
  71. panic(err)
  72. }
  73. return sz
  74. }
  75. func (r *region) Type() classType {
  76. return r.class
  77. }
  78. func newEmptyMemoryPool() *memoryPool {
  79. return &memoryPool{
  80. free: make(map[uint64]*region),
  81. busy: make(map[uint64]*region),
  82. }
  83. }
  84. func NewPoolMemoryAllocator() PoolAllocator {
  85. pa := PoolAllocator{}
  86. p := newEmptyMemoryPool()
  87. // by default we allocate a single region with maximum possible size (class type)
  88. p.free[0] = &region{
  89. class: memoryClassNumber - 1,
  90. offset: 0,
  91. }
  92. pa.pools[memoryClassNumber-1] = p
  93. return pa
  94. }
  95. // Allocate checks memory region pool for the given `size` and returns a free region with
  96. // minimal offset, if none available tries expanding matched memory pool.
  97. //
  98. // Internally it's done via moving a region from free pool into a busy pool
  99. func (pa *PoolAllocator) Allocate(size uint64) (MappedRegion, error) {
  100. memCls := GetMemoryClassType(size)
  101. if memCls >= memoryClassNumber {
  102. return nil, ErrInvalidMemoryClass
  103. }
  104. // find region with the smallest offset
  105. nextCls, nextOffset, err := pa.findNextOffset(memCls)
  106. if err != nil {
  107. return nil, err
  108. }
  109. // this means that there are no more regions for the current class, try expanding
  110. if nextCls != memCls {
  111. if err := pa.split(memCls); err != nil {
  112. if err == ErrInvalidMemoryClass {
  113. return nil, ErrNotEnoughSpace
  114. }
  115. return nil, err
  116. }
  117. }
  118. if err := pa.markBusy(memCls, nextOffset); err != nil {
  119. return nil, err
  120. }
  121. // by this point memory pool for memCls should have been created,
  122. // either prior or during split call
  123. if r := pa.pools[memCls].busy[nextOffset]; r != nil {
  124. return r, nil
  125. }
  126. return nil, ErrNotEnoughSpace
  127. }
  128. // Release marks a memory region of class `memCls` and offset `offset` as free and tries to merge smaller regions into
  129. // a bigger one
  130. func (pa *PoolAllocator) Release(reg MappedRegion) error {
  131. mp := pa.pools[reg.Type()]
  132. if mp == nil {
  133. return ErrEmptyPoolOperation
  134. }
  135. err := pa.markFree(reg.Type(), reg.Offset())
  136. if err != nil {
  137. return err
  138. }
  139. n := mp.free[reg.Offset()]
  140. if n == nil {
  141. return ErrNotAllocated
  142. }
  143. if err := pa.merge(n.parent); err != nil {
  144. if err != ErrEarlyMerge {
  145. return err
  146. }
  147. }
  148. return nil
  149. }
  150. // findNextOffset finds next region location for a given memCls
  151. func (pa *PoolAllocator) findNextOffset(memCls classType) (classType, uint64, error) {
  152. for mc := memCls; mc < memoryClassNumber; mc++ {
  153. pi := pa.pools[mc]
  154. if pi == nil || len(pi.free) == 0 {
  155. continue
  156. }
  157. target := uint64(maximumClassSize)
  158. for offset := range pi.free {
  159. if offset < target {
  160. target = offset
  161. }
  162. }
  163. return mc, target, nil
  164. }
  165. return 0, 0, ErrNotEnoughSpace
  166. }
  167. // split tries to recursively split a bigger memory region into smaller ones until it succeeds or hits the upper limit
  168. func (pa *PoolAllocator) split(clsType classType) error {
  169. nextClsType := clsType + 1
  170. if nextClsType >= memoryClassNumber {
  171. return ErrInvalidMemoryClass
  172. }
  173. nextPool := pa.pools[nextClsType]
  174. if nextPool == nil {
  175. nextPool = newEmptyMemoryPool()
  176. pa.pools[nextClsType] = nextPool
  177. }
  178. cls, offset, err := pa.findNextOffset(nextClsType)
  179. if err != nil {
  180. return err
  181. }
  182. // not enough memory in the next class, try to recursively expand
  183. if cls != nextClsType {
  184. if err := pa.split(nextClsType); err != nil {
  185. return err
  186. }
  187. }
  188. if err := pa.markBusy(nextClsType, offset); err != nil {
  189. return err
  190. }
  191. // memCls validity has been checked already, we can ignore the error
  192. clsSize, _ := GetMemoryClassSize(clsType)
  193. nextReg := nextPool.busy[offset]
  194. if nextReg == nil {
  195. return ErrNotAllocated
  196. }
  197. // expand memCls
  198. cp := pa.pools[clsType]
  199. if cp == nil {
  200. cp = newEmptyMemoryPool()
  201. pa.pools[clsType] = cp
  202. }
  203. // create 4 smaller regions
  204. for i := uint64(0); i < 4; i++ {
  205. offset := nextReg.offset + i*clsSize
  206. reg := &region{
  207. parent: nextReg,
  208. class: clsType,
  209. offset: offset,
  210. }
  211. cp.free[offset] = reg
  212. }
  213. return nil
  214. }
  215. func (pa *PoolAllocator) merge(parent *region) error {
  216. // nothing to merge
  217. if parent == nil {
  218. return nil
  219. }
  220. childCls := parent.class - 1
  221. childPool := pa.pools[childCls]
  222. // no child nodes to merge, try to merge parent
  223. if childPool == nil {
  224. return pa.merge(parent.parent)
  225. }
  226. childSize, err := GetMemoryClassSize(childCls)
  227. if err != nil {
  228. return err
  229. }
  230. // check if all the child nodes are free
  231. var children []*region
  232. for i := uint64(0); i < 4; i++ {
  233. child, free := childPool.free[parent.offset+i*childSize]
  234. if !free {
  235. return ErrEarlyMerge
  236. }
  237. children = append(children, child)
  238. }
  239. // at this point all the child nodes will be free and we can merge
  240. for _, child := range children {
  241. delete(childPool.free, child.offset)
  242. }
  243. if err := pa.markFree(parent.class, parent.offset); err != nil {
  244. return err
  245. }
  246. return pa.merge(parent.parent)
  247. }
  248. // markFree internally moves a region with `offset` from busy to free map
  249. func (pa *PoolAllocator) markFree(memCls classType, offset uint64) error {
  250. clsPool := pa.pools[memCls]
  251. if clsPool == nil {
  252. return ErrEmptyPoolOperation
  253. }
  254. if reg, exists := clsPool.busy[offset]; exists {
  255. clsPool.free[offset] = reg
  256. delete(clsPool.busy, offset)
  257. return nil
  258. }
  259. return ErrNotAllocated
  260. }
  261. // markBusy internally moves a region with `offset` from free to busy map
  262. func (pa *PoolAllocator) markBusy(memCls classType, offset uint64) error {
  263. clsPool := pa.pools[memCls]
  264. if clsPool == nil {
  265. return ErrEmptyPoolOperation
  266. }
  267. if reg, exists := clsPool.free[offset]; exists {
  268. clsPool.busy[offset] = reg
  269. delete(clsPool.free, offset)
  270. return nil
  271. }
  272. return ErrNotAllocated
  273. }