calloc_jemalloc.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. // Copyright 2020 The LevelDB-Go and Pebble Authors. All rights reserved. Use
  2. // of this source code is governed by a BSD-style license that can be found in
  3. // the LICENSE file.
  4. //go:build jemalloc
  5. // +build jemalloc
  6. package z
  7. /*
  8. #cgo LDFLAGS: /usr/local/lib/libjemalloc.a -L/usr/local/lib -Wl,-rpath,/usr/local/lib -ljemalloc -lm -lstdc++ -pthread -ldl
  9. #include <stdlib.h>
  10. #include <jemalloc/jemalloc.h>
  11. */
  12. import "C"
  13. import (
  14. "bytes"
  15. "fmt"
  16. "sync"
  17. "sync/atomic"
  18. "unsafe"
  19. "github.com/dustin/go-humanize"
  20. )
  21. // The go:linkname directives provides backdoor access to private functions in
  22. // the runtime. Below we're accessing the throw function.
  23. //go:linkname throw runtime.throw
  24. func throw(s string)
  25. // New allocates a slice of size n. The returned slice is from manually managed
  26. // memory and MUST be released by calling Free. Failure to do so will result in
  27. // a memory leak.
  28. //
  29. // Compile jemalloc with ./configure --with-jemalloc-prefix="je_"
  30. // https://android.googlesource.com/platform/external/jemalloc_new/+/6840b22e8e11cb68b493297a5cd757d6eaa0b406/TUNING.md
  31. // These two config options seems useful for frequent allocations and deallocations in
  32. // multi-threaded programs (like we have).
  33. // JE_MALLOC_CONF="background_thread:true,metadata_thp:auto"
  34. //
  35. // Compile Go program with `go build -tags=jemalloc` to enable this.
  36. type dalloc struct {
  37. t string
  38. sz int
  39. }
  40. var dallocsMu sync.Mutex
  41. var dallocs map[unsafe.Pointer]*dalloc
  42. func init() {
  43. // By initializing dallocs, we can start tracking allocations and deallocations via z.Calloc.
  44. dallocs = make(map[unsafe.Pointer]*dalloc)
  45. }
  46. func Calloc(n int, tag string) []byte {
  47. if n == 0 {
  48. return make([]byte, 0)
  49. }
  50. // We need to be conscious of the Cgo pointer passing rules:
  51. //
  52. // https://golang.org/cmd/cgo/#hdr-Passing_pointers
  53. //
  54. // ...
  55. // Note: the current implementation has a bug. While Go code is permitted
  56. // to write nil or a C pointer (but not a Go pointer) to C memory, the
  57. // current implementation may sometimes cause a runtime error if the
  58. // contents of the C memory appear to be a Go pointer. Therefore, avoid
  59. // passing uninitialized C memory to Go code if the Go code is going to
  60. // store pointer values in it. Zero out the memory in C before passing it
  61. // to Go.
  62. ptr := C.je_calloc(C.size_t(n), 1)
  63. if ptr == nil {
  64. // NB: throw is like panic, except it guarantees the process will be
  65. // terminated. The call below is exactly what the Go runtime invokes when
  66. // it cannot allocate memory.
  67. throw("out of memory")
  68. }
  69. uptr := unsafe.Pointer(ptr)
  70. dallocsMu.Lock()
  71. dallocs[uptr] = &dalloc{
  72. t: tag,
  73. sz: n,
  74. }
  75. dallocsMu.Unlock()
  76. atomic.AddInt64(&numBytes, int64(n))
  77. // Interpret the C pointer as a pointer to a Go array, then slice.
  78. return (*[MaxArrayLen]byte)(uptr)[:n:n]
  79. }
  80. // CallocNoRef does the exact same thing as Calloc with jemalloc enabled.
  81. func CallocNoRef(n int, tag string) []byte {
  82. return Calloc(n, tag)
  83. }
  84. // Free frees the specified slice.
  85. func Free(b []byte) {
  86. if sz := cap(b); sz != 0 {
  87. b = b[:cap(b)]
  88. ptr := unsafe.Pointer(&b[0])
  89. C.je_free(ptr)
  90. atomic.AddInt64(&numBytes, -int64(sz))
  91. dallocsMu.Lock()
  92. delete(dallocs, ptr)
  93. dallocsMu.Unlock()
  94. }
  95. }
  96. func Leaks() string {
  97. if dallocs == nil {
  98. return "Leak detection disabled. Enable with 'leak' build flag."
  99. }
  100. dallocsMu.Lock()
  101. defer dallocsMu.Unlock()
  102. if len(dallocs) == 0 {
  103. return "NO leaks found."
  104. }
  105. m := make(map[string]int)
  106. for _, da := range dallocs {
  107. m[da.t] += da.sz
  108. }
  109. var buf bytes.Buffer
  110. fmt.Fprintf(&buf, "Allocations:\n")
  111. for f, sz := range m {
  112. fmt.Fprintf(&buf, "%s at file: %s\n", humanize.IBytes(uint64(sz)), f)
  113. }
  114. return buf.String()
  115. }
  116. // ReadMemStats populates stats with JE Malloc statistics.
  117. func ReadMemStats(stats *MemStats) {
  118. if stats == nil {
  119. return
  120. }
  121. // Call an epoch mallclt to refresh the stats data as mentioned in the docs.
  122. // http://jemalloc.net/jemalloc.3.html#epoch
  123. // Note: This epoch mallctl is as expensive as a malloc call. It takes up the
  124. // malloc_mutex_lock.
  125. epoch := 1
  126. sz := unsafe.Sizeof(&epoch)
  127. C.je_mallctl(
  128. (C.CString)("epoch"),
  129. unsafe.Pointer(&epoch),
  130. (*C.size_t)(unsafe.Pointer(&sz)),
  131. unsafe.Pointer(&epoch),
  132. (C.size_t)(unsafe.Sizeof(epoch)))
  133. stats.Allocated = fetchStat("stats.allocated")
  134. stats.Active = fetchStat("stats.active")
  135. stats.Resident = fetchStat("stats.resident")
  136. stats.Retained = fetchStat("stats.retained")
  137. }
  138. // fetchStat is used to read a specific attribute from je malloc stats using mallctl.
  139. func fetchStat(s string) uint64 {
  140. var out uint64
  141. sz := unsafe.Sizeof(&out)
  142. C.je_mallctl(
  143. (C.CString)(s), // Query: eg: stats.allocated, stats.resident, etc.
  144. unsafe.Pointer(&out), // Variable to store the output.
  145. (*C.size_t)(unsafe.Pointer(&sz)), // Size of the output variable.
  146. nil, // Input variable used to set a value.
  147. 0) // Size of the input variable.
  148. return out
  149. }
  150. func StatsPrint() {
  151. opts := C.CString("mdablxe")
  152. C.je_malloc_stats_print(nil, nil, opts)
  153. C.free(unsafe.Pointer(opts))
  154. }