jemalloc_nodump_allocator.cc 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. // Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
  2. // This source code is licensed under both the GPLv2 (found in the
  3. // COPYING file in the root directory) and Apache 2.0 License
  4. // (found in the LICENSE.Apache file in the root directory).
  5. #include "memory/jemalloc_nodump_allocator.h"
  6. #include <string>
  7. #include <thread>
  8. #include "port/likely.h"
  9. #include "port/port.h"
  10. #include "util/string_util.h"
  11. namespace ROCKSDB_NAMESPACE {
  12. #ifdef ROCKSDB_JEMALLOC_NODUMP_ALLOCATOR
  13. std::atomic<extent_alloc_t*> JemallocNodumpAllocator::original_alloc_{nullptr};
  14. JemallocNodumpAllocator::JemallocNodumpAllocator(
  15. JemallocAllocatorOptions& options,
  16. std::unique_ptr<extent_hooks_t>&& arena_hooks, unsigned arena_index)
  17. : options_(options),
  18. arena_hooks_(std::move(arena_hooks)),
  19. arena_index_(arena_index),
  20. tcache_(&JemallocNodumpAllocator::DestroyThreadSpecificCache) {}
  21. int JemallocNodumpAllocator::GetThreadSpecificCache(size_t size) {
  22. // We always enable tcache. The only corner case is when there are a ton of
  23. // threads accessing with low frequency, then it could consume a lot of
  24. // memory (may reach # threads * ~1MB) without bringing too much benefit.
  25. if (options_.limit_tcache_size && (size <= options_.tcache_size_lower_bound ||
  26. size > options_.tcache_size_upper_bound)) {
  27. return MALLOCX_TCACHE_NONE;
  28. }
  29. unsigned* tcache_index = reinterpret_cast<unsigned*>(tcache_.Get());
  30. if (UNLIKELY(tcache_index == nullptr)) {
  31. // Instantiate tcache.
  32. tcache_index = new unsigned(0);
  33. size_t tcache_index_size = sizeof(unsigned);
  34. int ret =
  35. mallctl("tcache.create", tcache_index, &tcache_index_size, nullptr, 0);
  36. if (ret != 0) {
  37. // No good way to expose the error. Silently disable tcache.
  38. delete tcache_index;
  39. return MALLOCX_TCACHE_NONE;
  40. }
  41. tcache_.Reset(static_cast<void*>(tcache_index));
  42. }
  43. return MALLOCX_TCACHE(*tcache_index);
  44. }
  45. void* JemallocNodumpAllocator::Allocate(size_t size) {
  46. int tcache_flag = GetThreadSpecificCache(size);
  47. return mallocx(size, MALLOCX_ARENA(arena_index_) | tcache_flag);
  48. }
  49. void JemallocNodumpAllocator::Deallocate(void* p) {
  50. // Obtain tcache.
  51. size_t size = 0;
  52. if (options_.limit_tcache_size) {
  53. size = malloc_usable_size(p);
  54. }
  55. int tcache_flag = GetThreadSpecificCache(size);
  56. // No need to pass arena index to dallocx(). Jemalloc will find arena index
  57. // from its own metadata.
  58. dallocx(p, tcache_flag);
  59. }
  60. void* JemallocNodumpAllocator::Alloc(extent_hooks_t* extent, void* new_addr,
  61. size_t size, size_t alignment, bool* zero,
  62. bool* commit, unsigned arena_ind) {
  63. extent_alloc_t* original_alloc =
  64. original_alloc_.load(std::memory_order_relaxed);
  65. assert(original_alloc != nullptr);
  66. void* result = original_alloc(extent, new_addr, size, alignment, zero, commit,
  67. arena_ind);
  68. if (result != nullptr) {
  69. int ret = madvise(result, size, MADV_DONTDUMP);
  70. if (ret != 0) {
  71. fprintf(
  72. stderr,
  73. "JemallocNodumpAllocator failed to set MADV_DONTDUMP, error code: %d",
  74. ret);
  75. assert(false);
  76. }
  77. }
  78. return result;
  79. }
  80. Status JemallocNodumpAllocator::DestroyArena(unsigned arena_index) {
  81. assert(arena_index != 0);
  82. std::string key = "arena." + ToString(arena_index) + ".destroy";
  83. int ret = mallctl(key.c_str(), nullptr, 0, nullptr, 0);
  84. if (ret != 0) {
  85. return Status::Incomplete("Failed to destroy jemalloc arena, error code: " +
  86. ToString(ret));
  87. }
  88. return Status::OK();
  89. }
  90. void JemallocNodumpAllocator::DestroyThreadSpecificCache(void* ptr) {
  91. assert(ptr != nullptr);
  92. unsigned* tcache_index = static_cast<unsigned*>(ptr);
  93. size_t tcache_index_size = sizeof(unsigned);
  94. int ret __attribute__((__unused__)) =
  95. mallctl("tcache.destroy", nullptr, 0, tcache_index, tcache_index_size);
  96. // Silently ignore error.
  97. assert(ret == 0);
  98. delete tcache_index;
  99. }
  100. JemallocNodumpAllocator::~JemallocNodumpAllocator() {
  101. // Destroy tcache before destroying arena.
  102. autovector<void*> tcache_list;
  103. tcache_.Scrape(&tcache_list, nullptr);
  104. for (void* tcache_index : tcache_list) {
  105. DestroyThreadSpecificCache(tcache_index);
  106. }
  107. // Destroy arena. Silently ignore error.
  108. Status s __attribute__((__unused__)) = DestroyArena(arena_index_);
  109. assert(s.ok());
  110. }
  111. size_t JemallocNodumpAllocator::UsableSize(void* p,
  112. size_t /*allocation_size*/) const {
  113. return malloc_usable_size(static_cast<void*>(p));
  114. }
  115. #endif // ROCKSDB_JEMALLOC_NODUMP_ALLOCATOR
  116. Status NewJemallocNodumpAllocator(
  117. JemallocAllocatorOptions& options,
  118. std::shared_ptr<MemoryAllocator>* memory_allocator) {
  119. *memory_allocator = nullptr;
  120. Status unsupported = Status::NotSupported(
  121. "JemallocNodumpAllocator only available with jemalloc version >= 5 "
  122. "and MADV_DONTDUMP is available.");
  123. #ifndef ROCKSDB_JEMALLOC_NODUMP_ALLOCATOR
  124. (void)options;
  125. return unsupported;
  126. #else
  127. if (!HasJemalloc()) {
  128. return unsupported;
  129. }
  130. if (memory_allocator == nullptr) {
  131. return Status::InvalidArgument("memory_allocator must be non-null.");
  132. }
  133. if (options.limit_tcache_size &&
  134. options.tcache_size_lower_bound >= options.tcache_size_upper_bound) {
  135. return Status::InvalidArgument(
  136. "tcache_size_lower_bound larger or equal to tcache_size_upper_bound.");
  137. }
  138. // Create arena.
  139. unsigned arena_index = 0;
  140. size_t arena_index_size = sizeof(arena_index);
  141. int ret =
  142. mallctl("arenas.create", &arena_index, &arena_index_size, nullptr, 0);
  143. if (ret != 0) {
  144. return Status::Incomplete("Failed to create jemalloc arena, error code: " +
  145. ToString(ret));
  146. }
  147. assert(arena_index != 0);
  148. // Read existing hooks.
  149. std::string key = "arena." + ToString(arena_index) + ".extent_hooks";
  150. extent_hooks_t* hooks;
  151. size_t hooks_size = sizeof(hooks);
  152. ret = mallctl(key.c_str(), &hooks, &hooks_size, nullptr, 0);
  153. if (ret != 0) {
  154. JemallocNodumpAllocator::DestroyArena(arena_index);
  155. return Status::Incomplete("Failed to read existing hooks, error code: " +
  156. ToString(ret));
  157. }
  158. // Store existing alloc.
  159. extent_alloc_t* original_alloc = hooks->alloc;
  160. extent_alloc_t* expected = nullptr;
  161. bool success =
  162. JemallocNodumpAllocator::original_alloc_.compare_exchange_strong(
  163. expected, original_alloc);
  164. if (!success && original_alloc != expected) {
  165. JemallocNodumpAllocator::DestroyArena(arena_index);
  166. return Status::Incomplete("Original alloc conflict.");
  167. }
  168. // Set the custom hook.
  169. std::unique_ptr<extent_hooks_t> new_hooks(new extent_hooks_t(*hooks));
  170. new_hooks->alloc = &JemallocNodumpAllocator::Alloc;
  171. extent_hooks_t* hooks_ptr = new_hooks.get();
  172. ret = mallctl(key.c_str(), nullptr, nullptr, &hooks_ptr, sizeof(hooks_ptr));
  173. if (ret != 0) {
  174. JemallocNodumpAllocator::DestroyArena(arena_index);
  175. return Status::Incomplete("Failed to set custom hook, error code: " +
  176. ToString(ret));
  177. }
  178. // Create cache allocator.
  179. memory_allocator->reset(
  180. new JemallocNodumpAllocator(options, std::move(new_hooks), arena_index));
  181. return Status::OK();
  182. #endif // ROCKSDB_JEMALLOC_NODUMP_ALLOCATOR
  183. }
  184. } // namespace ROCKSDB_NAMESPACE