JemallocHugePageAllocator.cpp 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. /*
  2. * Copyright 2018-present Facebook, Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #include <folly/experimental/JemallocHugePageAllocator.h>
  17. #include <folly/portability/String.h>
  18. #include <glog/logging.h>
  19. #include <sstream>
  20. #if defined(MADV_HUGEPAGE) && defined(FOLLY_USE_JEMALLOC) && !FOLLY_SANITIZE
  21. #include <jemalloc/jemalloc.h>
  22. #if (JEMALLOC_VERSION_MAJOR >= 5)
  23. #define FOLLY_JEMALLOC_HUGE_PAGE_ALLOCATOR_SUPPORTED 1
  24. bool folly::JemallocHugePageAllocator::hugePagesSupported{true};
  25. #endif
  26. #endif // defined(FOLLY_HAVE_LIBJEMALLOC) && !FOLLY_SANITIZE
  27. #ifndef FOLLY_JEMALLOC_HUGE_PAGE_ALLOCATOR_SUPPORTED
  28. // Some mocks when jemalloc.h is not included or version too old
  29. // or when the system does not support the MADV_HUGEPAGE madvise flag
  30. #undef MALLOCX_ARENA
  31. #undef MALLOCX_TCACHE_NONE
  32. #undef MADV_HUGEPAGE
  33. #define MALLOCX_ARENA(x) 0
  34. #define MALLOCX_TCACHE_NONE 0
  35. #define MADV_HUGEPAGE 0
  36. typedef struct extent_hooks_s extent_hooks_t;
  37. typedef void*(extent_alloc_t)(
  38. extent_hooks_t*,
  39. void*,
  40. size_t,
  41. size_t,
  42. bool*,
  43. bool*,
  44. unsigned);
  45. struct extent_hooks_s {
  46. extent_alloc_t* alloc;
  47. };
  48. bool folly::JemallocHugePageAllocator::hugePagesSupported{false};
  49. #endif // FOLLY_JEMALLOC_HUGE_PAGE_ALLOCATOR_SUPPORTED
  50. namespace folly {
  51. namespace {
  52. static void print_error(int err, const char* msg) {
  53. int cur_errno = std::exchange(errno, err);
  54. PLOG(ERROR) << msg;
  55. errno = cur_errno;
  56. }
  57. class HugePageArena {
  58. public:
  59. int init(int nr_pages);
  60. void* reserve(size_t size, size_t alignment);
  61. bool addressInArena(void* address) {
  62. uintptr_t addr = reinterpret_cast<uintptr_t>(address);
  63. return addr >= start_ && addr < end_;
  64. }
  65. size_t freeSpace() {
  66. return end_ - freePtr_;
  67. }
  68. private:
  69. static void* allocHook(
  70. extent_hooks_t* extent,
  71. void* new_addr,
  72. size_t size,
  73. size_t alignment,
  74. bool* zero,
  75. bool* commit,
  76. unsigned arena_ind);
  77. uintptr_t start_{0};
  78. uintptr_t end_{0};
  79. uintptr_t freePtr_{0};
  80. extent_alloc_t* originalAlloc_{nullptr};
  81. extent_hooks_t extentHooks_;
  82. };
  83. constexpr size_t kHugePageSize = 2 * 1024 * 1024;
  84. // Singleton arena instance
  85. static HugePageArena arena;
  86. template <typename T, typename U>
  87. static inline T align_up(T val, U alignment) {
  88. DCHECK((alignment & (alignment - 1)) == 0);
  89. return (val + alignment - 1) & ~(alignment - 1);
  90. }
  91. // mmap enough memory to hold the aligned huge pages, then use madvise
  92. // to get huge pages. Note that this is only a hint and is not guaranteed
  93. // to be honoured. Check /proc/<pid>/smaps to verify!
  94. static uintptr_t map_pages(size_t nr_pages) {
  95. // Initial mmapped area is large enough to contain the aligned huge pages
  96. size_t alloc_size = nr_pages * kHugePageSize;
  97. void* p = mmap(
  98. nullptr,
  99. alloc_size + kHugePageSize,
  100. PROT_READ | PROT_WRITE,
  101. MAP_PRIVATE | MAP_ANONYMOUS,
  102. -1,
  103. 0);
  104. if (p == MAP_FAILED) {
  105. return 0;
  106. }
  107. // Aligned start address
  108. uintptr_t first_page = align_up((uintptr_t)p, kHugePageSize);
  109. // Unmap left-over 4k pages
  110. munmap(p, first_page - (uintptr_t)p);
  111. munmap(
  112. (void*)(first_page + alloc_size),
  113. kHugePageSize - (first_page - (uintptr_t)p));
  114. // Tell the kernel to please give us huge pages for this range
  115. madvise((void*)first_page, kHugePageSize * nr_pages, MADV_HUGEPAGE);
  116. LOG(INFO) << nr_pages << " huge pages at " << (void*)first_page;
  117. return first_page;
  118. }
  119. void* HugePageArena::allocHook(
  120. extent_hooks_t* extent,
  121. void* new_addr,
  122. size_t size,
  123. size_t alignment,
  124. bool* zero,
  125. bool* commit,
  126. unsigned arena_ind) {
  127. DCHECK((size & (size - 1)) == 0);
  128. void* res = nullptr;
  129. if (new_addr == nullptr) {
  130. res = arena.reserve(size, alignment);
  131. }
  132. LOG(INFO) << "Extent request of size " << size << " alignment " << alignment
  133. << " = " << res << " (" << arena.freeSpace() << " bytes free)";
  134. if (res == nullptr) {
  135. LOG_IF(WARNING, new_addr != nullptr) << "Explicit address not supported";
  136. res = arena.originalAlloc_(
  137. extent, new_addr, size, alignment, zero, commit, arena_ind);
  138. } else {
  139. if (*zero) {
  140. bzero(res, size);
  141. }
  142. *commit = true;
  143. }
  144. return res;
  145. }
  146. int HugePageArena::init(int nr_pages) {
  147. DCHECK(start_ == 0);
  148. DCHECK(usingJEMalloc());
  149. unsigned arena_index;
  150. size_t len = sizeof(arena_index);
  151. if (auto ret = mallctl("arenas.create", &arena_index, &len, nullptr, 0)) {
  152. print_error(ret, "Unable to create arena");
  153. return 0;
  154. }
  155. // Set grow retained limit to stop jemalloc from
  156. // forever increasing the requested size after failed allocations.
  157. // Normally jemalloc asks for maps of increasing size in order to avoid
  158. // hitting the limit of allowed mmaps per process.
  159. // Since this arena is backed by a single mmap and is using huge pages,
  160. // this is not a concern here.
  161. // TODO: Support growth of the huge page arena.
  162. size_t mib[3];
  163. size_t miblen = sizeof(mib) / sizeof(size_t);
  164. std::ostringstream rtl_key;
  165. rtl_key << "arena." << arena_index << ".retain_grow_limit";
  166. if (auto ret = mallctlnametomib(rtl_key.str().c_str(), mib, &miblen)) {
  167. print_error(ret, "Unable to read growth limit");
  168. return 0;
  169. }
  170. size_t grow_retained_limit = kHugePageSize;
  171. mib[1] = arena_index;
  172. if (auto ret = mallctlbymib(
  173. mib,
  174. miblen,
  175. nullptr,
  176. nullptr,
  177. &grow_retained_limit,
  178. sizeof(grow_retained_limit))) {
  179. print_error(ret, "Unable to set growth limit");
  180. return 0;
  181. }
  182. std::ostringstream hooks_key;
  183. hooks_key << "arena." << arena_index << ".extent_hooks";
  184. extent_hooks_t* hooks;
  185. len = sizeof(hooks);
  186. // Read the existing hooks
  187. if (auto ret = mallctl(hooks_key.str().c_str(), &hooks, &len, nullptr, 0)) {
  188. print_error(ret, "Unable to get the hooks");
  189. return 0;
  190. }
  191. originalAlloc_ = hooks->alloc;
  192. // Set the custom hook
  193. extentHooks_ = *hooks;
  194. extentHooks_.alloc = &allocHook;
  195. extent_hooks_t* new_hooks = &extentHooks_;
  196. if (auto ret = mallctl(
  197. hooks_key.str().c_str(),
  198. nullptr,
  199. nullptr,
  200. &new_hooks,
  201. sizeof(new_hooks))) {
  202. print_error(ret, "Unable to set the hooks");
  203. return 0;
  204. }
  205. start_ = freePtr_ = map_pages(nr_pages);
  206. if (start_ == 0) {
  207. return false;
  208. }
  209. end_ = start_ + (nr_pages * kHugePageSize);
  210. return MALLOCX_ARENA(arena_index) | MALLOCX_TCACHE_NONE;
  211. }
  212. void* HugePageArena::reserve(size_t size, size_t alignment) {
  213. VLOG(1) << "Reserve: " << size << " alignemnt " << alignment;
  214. uintptr_t res = align_up(freePtr_, alignment);
  215. uintptr_t newFreePtr = res + size;
  216. if (newFreePtr > end_) {
  217. LOG(WARNING) << "Request of size " << size << " denied: " << freeSpace()
  218. << " bytes available - not backed by huge pages";
  219. return nullptr;
  220. }
  221. freePtr_ = newFreePtr;
  222. return reinterpret_cast<void*>(res);
  223. }
  224. } // namespace
  225. int JemallocHugePageAllocator::flags_{0};
  226. bool JemallocHugePageAllocator::init(int nr_pages) {
  227. if (!usingJEMalloc()) {
  228. LOG(ERROR) << "Not linked with jemalloc?";
  229. hugePagesSupported = false;
  230. }
  231. if (hugePagesSupported) {
  232. if (flags_ == 0) {
  233. flags_ = arena.init(nr_pages);
  234. } else {
  235. LOG(WARNING) << "Already initialized";
  236. }
  237. } else {
  238. LOG(WARNING) << "Huge Page Allocator not supported";
  239. }
  240. return flags_ != 0;
  241. }
  242. size_t JemallocHugePageAllocator::freeSpace() {
  243. return arena.freeSpace();
  244. }
  245. bool JemallocHugePageAllocator::addressInArena(void* address) {
  246. return arena.addressInArena(address);
  247. }
  248. } // namespace folly