memory.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. // Copyright 2017 The Memory Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. // Package memory implements a memory allocator.
  5. //
  6. // # Build status
  7. //
  8. // available at https://modern-c.appspot.com/-/builder/?importpath=modernc.org%2fmemory
  9. //
  10. // # Changelog
  11. //
  12. // 2017-10-03 Added alternative, unsafe.Pointer-based API.
  13. //
  14. // Package memory implements a memory allocator.
  15. //
  16. // # Changelog
  17. //
  18. // 2017-10-03 Added alternative, unsafe.Pointer-based API.
  19. //
  20. // # Benchmarks
  21. //
  22. // jnml@3900x:~/src/modernc.org/memory$ date ; go version ; go test -run @ -bench . -benchmem |& tee log
  23. // Mon Sep 25 16:02:02 CEST 2023
  24. // go version go1.21.1 linux/amd64
  25. // goos: linux
  26. // goarch: amd64
  27. // pkg: modernc.org/memory
  28. // cpu: AMD Ryzen 9 3900X 12-Core Processor
  29. // BenchmarkFree16-24 123506772 9.802 ns/op 0 B/op 0 allocs/op
  30. // BenchmarkFree32-24 73853230 15.08 ns/op 0 B/op 0 allocs/op
  31. // BenchmarkFree64-24 43070334 25.15 ns/op 0 B/op 0 allocs/op
  32. // BenchmarkCalloc16-24 59353304 18.92 ns/op 0 B/op 0 allocs/op
  33. // BenchmarkCalloc32-24 39415004 29.00 ns/op 0 B/op 0 allocs/op
  34. // BenchmarkCalloc64-24 35825725 32.02 ns/op 0 B/op 0 allocs/op
  35. // BenchmarkGoCalloc16-24 38274313 26.99 ns/op 16 B/op 1 allocs/op
  36. // BenchmarkGoCalloc32-24 44590477 33.06 ns/op 32 B/op 1 allocs/op
  37. // BenchmarkGoCalloc64-24 44233016 37.20 ns/op 64 B/op 1 allocs/op
  38. // BenchmarkMalloc16-24 145736911 7.720 ns/op 0 B/op 0 allocs/op
  39. // BenchmarkMalloc32-24 128898334 7.887 ns/op 0 B/op 0 allocs/op
  40. // BenchmarkMalloc64-24 149569483 7.994 ns/op 0 B/op 0 allocs/op
  41. // BenchmarkUintptrFree16-24 117043012 9.205 ns/op 0 B/op 0 allocs/op
  42. // BenchmarkUintptrFree32-24 77399617 14.20 ns/op 0 B/op 0 allocs/op
  43. // BenchmarkUintptrFree64-24 48770785 25.04 ns/op 0 B/op 0 allocs/op
  44. // BenchmarkUintptrCalloc16-24 79257636 15.44 ns/op 0 B/op 0 allocs/op
  45. // BenchmarkUintptrCalloc32-24 49644562 23.62 ns/op 0 B/op 0 allocs/op
  46. // BenchmarkUintptrCalloc64-24 39854710 28.22 ns/op 0 B/op 0 allocs/op
  47. // BenchmarkUintptrMalloc16-24 252987727 4.525 ns/op 0 B/op 0 allocs/op
  48. // BenchmarkUintptrMalloc32-24 241423840 4.433 ns/op 0 B/op 0 allocs/op
  49. // BenchmarkUintptrMalloc64-24 256450324 4.669 ns/op 0 B/op 0 allocs/op
  50. // PASS
  51. // ok modernc.org/memory 93.178s
  52. // jnml@3900x:~/src/modernc.org/memory$
  53. package memory // import "modernc.org/memory"
  54. import (
  55. "fmt"
  56. "math/bits"
  57. "os"
  58. "unsafe"
  59. )
  60. const (
  61. headerSize = unsafe.Sizeof(page{})
  62. mallocAllign = 2 * unsafe.Sizeof(uintptr(0))
  63. maxSlotSize = 1 << maxSlotSizeLog
  64. maxSlotSizeLog = pageSizeLog - 2
  65. pageAvail = pageSize - headerSize
  66. pageMask = pageSize - 1
  67. pageSize = 1 << pageSizeLog
  68. )
  69. func init() {
  70. if unsafe.Sizeof(page{})%mallocAllign != 0 {
  71. panic("internal error")
  72. }
  73. }
  74. // if n%m != 0 { n += m-n%m }. m must be a power of 2.
  75. func roundup(n, m int) int { return (n + m - 1) &^ (m - 1) }
  76. type node struct {
  77. prev, next uintptr // *node
  78. }
  79. type page struct {
  80. brk int
  81. log uint
  82. size int
  83. used int
  84. }
  85. // Allocator allocates and frees memory. Its zero value is ready for use. The
  86. // exported counters are updated only when build tag memory.counters is
  87. // present.
  88. type Allocator struct {
  89. Allocs int // # of allocs.
  90. Bytes int // Asked from OS.
  91. cap [64]int
  92. lists [64]uintptr // *node
  93. Mmaps int // Asked from OS.
  94. pages [64]uintptr // *page
  95. regs map[uintptr]struct{} // map[*page]struct{}
  96. }
  97. func (a *Allocator) mmap(size int) (uintptr /* *page */, error) {
  98. p, size, err := mmap(size)
  99. if err != nil {
  100. return 0, err
  101. }
  102. //TODO(jnml) The returned size may now be nearly as twice as large as we asked
  103. //for. Use that extra capacity. For that we need to move the respective
  104. //Allocator.cap item into the page struct so the page cap becomes dynamic.
  105. //
  106. // Related: This is a consequence of fixing the bigsort.test failures on
  107. // linux/s390x, see: https://gitlab.com/cznic/sqlite/-/issues/207
  108. if counters {
  109. a.Mmaps++
  110. a.Bytes += size
  111. }
  112. if a.regs == nil {
  113. a.regs = map[uintptr]struct{}{}
  114. }
  115. (*page)(unsafe.Pointer(p)).size = size
  116. a.regs[p] = struct{}{}
  117. return p, nil
  118. }
  119. func (a *Allocator) newPage(size int) (uintptr /* *page */, error) {
  120. size += int(headerSize)
  121. p, err := a.mmap(size)
  122. if err != nil {
  123. return 0, err
  124. }
  125. (*page)(unsafe.Pointer(p)).log = 0
  126. return p, nil
  127. }
  128. func (a *Allocator) newSharedPage(log uint) (uintptr /* *page */, error) {
  129. if a.cap[log] == 0 {
  130. a.cap[log] = int(pageAvail) / (1 << log)
  131. }
  132. size := int(headerSize) + a.cap[log]<<log
  133. p, err := a.mmap(size)
  134. if err != nil {
  135. return 0, err
  136. }
  137. a.pages[log] = p
  138. (*page)(unsafe.Pointer(p)).log = log
  139. return p, nil
  140. }
  141. func (a *Allocator) unmap(p uintptr /* *page */) error {
  142. delete(a.regs, p)
  143. if counters {
  144. a.Mmaps--
  145. }
  146. return unmap(p, (*page)(unsafe.Pointer(p)).size)
  147. }
  148. // UintptrCalloc is like Calloc except it returns an uintptr.
  149. func (a *Allocator) UintptrCalloc(size int) (r uintptr, err error) {
  150. if trace {
  151. defer func() {
  152. fmt.Fprintf(os.Stderr, "Calloc(%#x) %#x, %v\n", size, r, err)
  153. }()
  154. }
  155. if r, err = a.UintptrMalloc(size); r == 0 || err != nil {
  156. return 0, err
  157. }
  158. b := ((*rawmem)(unsafe.Pointer(r)))[:size:size]
  159. for i := range b {
  160. b[i] = 0
  161. }
  162. return r, nil
  163. }
  164. // UintptrFree is like Free except its argument is an uintptr, which must have
  165. // been acquired from UintptrCalloc or UintptrMalloc or UintptrRealloc.
  166. func (a *Allocator) UintptrFree(p uintptr) (err error) {
  167. if trace {
  168. defer func() {
  169. fmt.Fprintf(os.Stderr, "Free(%#x) %v\n", p, err)
  170. }()
  171. }
  172. if p == 0 {
  173. return nil
  174. }
  175. if counters {
  176. a.Allocs--
  177. }
  178. pg := p &^ uintptr(pageMask)
  179. log := (*page)(unsafe.Pointer(pg)).log
  180. if log == 0 {
  181. if counters {
  182. a.Bytes -= (*page)(unsafe.Pointer(pg)).size
  183. }
  184. return a.unmap(pg)
  185. }
  186. (*node)(unsafe.Pointer(p)).prev = 0
  187. (*node)(unsafe.Pointer(p)).next = a.lists[log]
  188. if next := (*node)(unsafe.Pointer(p)).next; next != 0 {
  189. (*node)(unsafe.Pointer(next)).prev = p
  190. }
  191. a.lists[log] = p
  192. (*page)(unsafe.Pointer(pg)).used--
  193. if (*page)(unsafe.Pointer(pg)).used != 0 {
  194. return nil
  195. }
  196. for i := 0; i < (*page)(unsafe.Pointer(pg)).brk; i++ {
  197. n := pg + headerSize + uintptr(i)<<log
  198. next := (*node)(unsafe.Pointer(n)).next
  199. prev := (*node)(unsafe.Pointer(n)).prev
  200. switch {
  201. case prev == 0:
  202. a.lists[log] = next
  203. if next != 0 {
  204. (*node)(unsafe.Pointer(next)).prev = 0
  205. }
  206. case next == 0:
  207. (*node)(unsafe.Pointer(prev)).next = 0
  208. default:
  209. (*node)(unsafe.Pointer(prev)).next = next
  210. (*node)(unsafe.Pointer(next)).prev = prev
  211. }
  212. }
  213. if a.pages[log] == pg {
  214. a.pages[log] = 0
  215. }
  216. if counters {
  217. a.Bytes -= (*page)(unsafe.Pointer(pg)).size
  218. }
  219. return a.unmap(pg)
  220. }
  221. // UintptrMalloc is like Malloc except it returns an uinptr.
  222. func (a *Allocator) UintptrMalloc(size int) (r uintptr, err error) {
  223. if trace {
  224. defer func() {
  225. fmt.Fprintf(os.Stderr, "Malloc(%#x) %#x, %v\n", size, r, err)
  226. }()
  227. }
  228. if size < 0 {
  229. panic("invalid malloc size")
  230. }
  231. if size == 0 {
  232. return 0, nil
  233. }
  234. if counters {
  235. a.Allocs++
  236. }
  237. log := uint(bits.Len(uint((size+int(mallocAllign)-1)&^int(mallocAllign-1) - 1)))
  238. if log > maxSlotSizeLog {
  239. p, err := a.newPage(size)
  240. if err != nil {
  241. return 0, err
  242. }
  243. return p + headerSize, nil
  244. }
  245. if a.lists[log] == 0 && a.pages[log] == 0 {
  246. if _, err := a.newSharedPage(log); err != nil {
  247. return 0, err
  248. }
  249. }
  250. if p := a.pages[log]; p != 0 {
  251. (*page)(unsafe.Pointer(p)).used++
  252. (*page)(unsafe.Pointer(p)).brk++
  253. if (*page)(unsafe.Pointer(p)).brk == a.cap[log] {
  254. a.pages[log] = 0
  255. }
  256. return p + headerSize + uintptr((*page)(unsafe.Pointer(p)).brk-1)<<log, nil
  257. }
  258. n := a.lists[log]
  259. p := n &^ uintptr(pageMask)
  260. a.lists[log] = (*node)(unsafe.Pointer(n)).next
  261. if next := (*node)(unsafe.Pointer(n)).next; next != 0 {
  262. (*node)(unsafe.Pointer(next)).prev = 0
  263. }
  264. (*page)(unsafe.Pointer(p)).used++
  265. return n, nil
  266. }
  267. // UintptrRealloc is like Realloc except its first argument is an uintptr,
  268. // which must have been returned from UintptrCalloc, UintptrMalloc or
  269. // UintptrRealloc.
  270. func (a *Allocator) UintptrRealloc(p uintptr, size int) (r uintptr, err error) {
  271. if trace {
  272. defer func() {
  273. fmt.Fprintf(os.Stderr, "UnsafeRealloc(%#x, %#x) %#x, %v\n", p, size, r, err)
  274. }()
  275. }
  276. switch {
  277. case p == 0:
  278. return a.UintptrMalloc(size)
  279. case size == 0 && p != 0:
  280. return 0, a.UintptrFree(p)
  281. }
  282. us := UintptrUsableSize(p)
  283. if us >= size {
  284. return p, nil
  285. }
  286. if r, err = a.UintptrMalloc(size); err != nil {
  287. return 0, err
  288. }
  289. if us < size {
  290. size = us
  291. }
  292. copy((*rawmem)(unsafe.Pointer(r))[:size:size], (*rawmem)(unsafe.Pointer(p))[:size:size])
  293. return r, a.UintptrFree(p)
  294. }
  295. // UintptrUsableSize is like UsableSize except its argument is an uintptr,
  296. // which must have been returned from UintptrCalloc, UintptrMalloc or
  297. // UintptrRealloc.
  298. func UintptrUsableSize(p uintptr) (r int) {
  299. if trace {
  300. defer func() {
  301. fmt.Fprintf(os.Stderr, "UsableSize(%#x) %#x\n", p, r)
  302. }()
  303. }
  304. if p == 0 {
  305. return 0
  306. }
  307. return usableSize(p)
  308. }
  309. func usableSize(p uintptr) (r int) {
  310. pg := p &^ uintptr(pageMask)
  311. if log := (*page)(unsafe.Pointer(pg)).log; log != 0 {
  312. return 1 << log
  313. }
  314. return (*page)(unsafe.Pointer(pg)).size - int(headerSize)
  315. }
  316. // Calloc is like Malloc except the allocated memory is zeroed.
  317. func (a *Allocator) Calloc(size int) (r []byte, err error) {
  318. p, err := a.UintptrCalloc(size)
  319. if err != nil {
  320. return nil, err
  321. }
  322. return (*rawmem)(unsafe.Pointer(p))[:size:usableSize(p)], nil
  323. }
  324. // Close releases all OS resources used by a and sets it to its zero value.
  325. //
  326. // It's not necessary to Close the Allocator when exiting a process.
  327. func (a *Allocator) Close() (err error) {
  328. for p := range a.regs {
  329. if e := a.unmap(p); e != nil && err == nil {
  330. err = e
  331. }
  332. }
  333. *a = Allocator{}
  334. return err
  335. }
  336. // Free deallocates memory (as in C.free). The argument of Free must have been
  337. // acquired from Calloc or Malloc or Realloc.
  338. func (a *Allocator) Free(b []byte) (err error) {
  339. if b = b[:cap(b)]; len(b) == 0 {
  340. return nil
  341. }
  342. return a.UintptrFree(uintptr(unsafe.Pointer(&b[0])))
  343. }
  344. // Malloc allocates size bytes and returns a byte slice of the allocated
  345. // memory. The memory is not initialized. Malloc panics for size < 0 and
  346. // returns (nil, nil) for zero size.
  347. //
  348. // It's ok to reslice the returned slice but the result of appending to it
  349. // cannot be passed to Free or Realloc as it may refer to a different backing
  350. // array afterwards.
  351. func (a *Allocator) Malloc(size int) (r []byte, err error) {
  352. p, err := a.UintptrMalloc(size)
  353. if p == 0 || err != nil {
  354. return nil, err
  355. }
  356. return (*rawmem)(unsafe.Pointer(p))[:size:usableSize(p)], nil
  357. }
  358. // Realloc changes the size of the backing array of b to size bytes or returns
  359. // an error, if any. The contents will be unchanged in the range from the
  360. // start of the region up to the minimum of the old and new sizes. If the
  361. // new size is larger than the old size, the added memory will not be
  362. // initialized. If b's backing array is of zero size, then the call is
  363. // equivalent to Malloc(size), for all values of size; if size is equal to
  364. // zero, and b's backing array is not of zero size, then the call is equivalent
  365. // to Free(b). Unless b's backing array is of zero size, it must have been
  366. // returned by an earlier call to Malloc, Calloc or Realloc. If the area
  367. // pointed to was moved, a Free(b) is done.
  368. func (a *Allocator) Realloc(b []byte, size int) (r []byte, err error) {
  369. var p uintptr
  370. if b = b[:cap(b)]; len(b) != 0 {
  371. p = uintptr(unsafe.Pointer(&b[0]))
  372. }
  373. if p, err = a.UintptrRealloc(p, size); p == 0 || err != nil {
  374. return nil, err
  375. }
  376. return (*rawmem)(unsafe.Pointer(p))[:size:usableSize(p)], nil
  377. }
  378. // UsableSize reports the size of the memory block allocated at p, which must
  379. // point to the first byte of a slice returned from Calloc, Malloc or Realloc.
  380. // The allocated memory block size can be larger than the size originally
  381. // requested from Calloc, Malloc or Realloc.
  382. func UsableSize(p *byte) (r int) { return UintptrUsableSize(uintptr(unsafe.Pointer(p))) }
  383. // UnsafeCalloc is like Calloc except it returns an unsafe.Pointer.
  384. func (a *Allocator) UnsafeCalloc(size int) (r unsafe.Pointer, err error) {
  385. p, err := a.UintptrCalloc(size)
  386. if err != nil {
  387. return nil, err
  388. }
  389. return unsafe.Pointer(p), nil
  390. }
  391. // UnsafeFree is like Free except its argument is an unsafe.Pointer, which must
  392. // have been acquired from UnsafeCalloc or UnsafeMalloc or UnsafeRealloc.
  393. func (a *Allocator) UnsafeFree(p unsafe.Pointer) (err error) { return a.UintptrFree(uintptr(p)) }
  394. // UnsafeMalloc is like Malloc except it returns an unsafe.Pointer.
  395. func (a *Allocator) UnsafeMalloc(size int) (r unsafe.Pointer, err error) {
  396. p, err := a.UintptrMalloc(size)
  397. if err != nil {
  398. return nil, err
  399. }
  400. return unsafe.Pointer(p), nil
  401. }
  402. // UnsafeRealloc is like Realloc except its first argument is an
  403. // unsafe.Pointer, which must have been returned from UnsafeCalloc,
  404. // UnsafeMalloc or UnsafeRealloc.
  405. func (a *Allocator) UnsafeRealloc(p unsafe.Pointer, size int) (r unsafe.Pointer, err error) {
  406. q, err := a.UintptrRealloc(uintptr(p), size)
  407. if err != nil {
  408. return nil, err
  409. }
  410. return unsafe.Pointer(q), nil
  411. }
  412. // UnsafeUsableSize is like UsableSize except its argument is an
  413. // unsafe.Pointer, which must have been returned from UnsafeCalloc,
  414. // UnsafeMalloc or UnsafeRealloc.
  415. func UnsafeUsableSize(p unsafe.Pointer) (r int) { return UintptrUsableSize(uintptr(p)) }