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