hphp/util/alloc.cpp (680 lines of code) (raw):

/* +----------------------------------------------------------------------+ | HipHop for PHP | +----------------------------------------------------------------------+ | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ */ #include "hphp/util/alloc.h" #include <atomic> #include <mutex> #include <errno.h> #include <signal.h> #include <stdlib.h> #include <unistd.h> #ifdef __APPLE__ #include <sys/sysctl.h> #endif #include <folly/portability/SysMman.h> #include <folly/portability/SysResource.h> #include "hphp/util/address-range.h" #include "hphp/util/bump-mapper.h" #include "hphp/util/extent-hooks.h" #include "hphp/util/hugetlb.h" #include "hphp/util/kernel-version.h" #include "hphp/util/managed-arena.h" #include "hphp/util/numa.h" #include "hphp/util/slab-manager.h" namespace HPHP { /////////////////////////////////////////////////////////////////////////////// void flush_thread_caches() { #ifdef USE_JEMALLOC mallctlCall<true>("thread.tcache.flush"); #if USE_JEMALLOC_EXTENT_HOOKS arenas_thread_flush(); #endif #endif } __thread int32_t s_numaNode; __thread uintptr_t s_stackLimit; __thread size_t s_stackSize; const size_t s_pageSize = sysconf(_SC_PAGESIZE); __thread MemBlock s_tlSpace; __thread MemBlock s_hugeRange; __thread TLStaticArena* tl_static_arena; bool s_enable_static_arena = false; static NEVER_INLINE uintptr_t get_stack_top() { using ActRec = char; DECLARE_FRAME_POINTER(fp); return uintptr_t(fp) - s_pageSize; } void init_stack_limits(pthread_attr_t* attr) { size_t stacksize, guardsize; void *stackaddr; struct rlimit rlim; #ifndef __APPLE__ if (pthread_attr_getstack(attr, &stackaddr, &stacksize) != 0) { always_assert(false); } #else // We must use the following (undocumented) APIs because pthread_attr_getstack // returns incorrect values on OSX. pthread_t self = pthread_self(); stackaddr = pthread_get_stackaddr_np(self); stacksize = pthread_get_stacksize_np(self); // On OSX 10.9, we are lied to about the main thread's stack size. Set it to // the minimum stack size, which is set earlier by execute_program_impl. if (pthread_main_np() == 1) { if (s_stackSize < kStackSizeMinimum) { char osRelease[256]; size_t osReleaseSize = sizeof(osRelease); if (sysctlbyname("kern.osrelease", osRelease, &osReleaseSize, nullptr, 0) == 0) { if (atoi(osRelease) >= 13) { stacksize = kStackSizeMinimum; } } } } // stackaddr is not base, but top of the stack. Yes, really. stackaddr = ((char*) stackaddr) - stacksize; #endif // Get the guard page's size, because the stack address returned // above starts at the guard page, so the thread's stack limit is // stackaddr + guardsize. if (pthread_attr_getguardsize(attr, &guardsize) != 0) { guardsize = 0; } assert(stackaddr != nullptr); assert(stacksize >= PTHREAD_STACK_MIN); s_stackLimit = uintptr_t(stackaddr) + guardsize; s_stackSize = stacksize - guardsize; // The main thread's native stack may be larger than desired if // set_stack_size() failed. Make sure that even if the native stack is // extremely large (in which case anonymous mmap() could map some of the // "stack space"), we can differentiate between the part of the native stack // that could conceivably be used in practice and all anonymous mmap() memory. if (getrlimit(RLIMIT_STACK, &rlim) == 0 && rlim.rlim_cur == RLIM_INFINITY && s_stackSize > kStackSizeMinimum) { s_stackLimit += s_stackSize - kStackSizeMinimum; s_stackSize = kStackSizeMinimum; } } void flush_thread_stack() { uintptr_t top = get_stack_top() & (s_pageSize - 1); auto const hugeBase = reinterpret_cast<uintptr_t>(s_hugeRange.ptr); if (top > hugeBase) top = hugeBase; if (top <= s_stackLimit) return; size_t len = top - s_stackLimit; if (madvise((void*)s_stackLimit, len, MADV_DONTNEED) != 0 && errno != EAGAIN) { fprintf(stderr, "%s failed to madvise with error %d\n", __func__, errno); } } ssize_t purgeable_bytes() { #ifdef USE_JEMALLOC return s_pageSize * mallctl_all_pdirty(); #else return 0; #endif } #if !defined USE_JEMALLOC || !defined HAVE_NUMA void set_numa_binding(int node) {} void* mallocx_on_node(size_t size, int node, size_t align) { void* ret = nullptr; posix_memalign(&ret, align, size); return ret; } #endif #ifdef USE_JEMALLOC unsigned low_arena = 0; unsigned lower_arena = 0; unsigned low_cold_arena = 0; unsigned high_arena = 0; unsigned high_cold_arena = 0; __thread unsigned local_arena = 0; int low_arena_flags = 0; int lower_arena_flags = 0; int low_cold_arena_flags = 0; int high_cold_arena_flags = 0; __thread int high_arena_flags = 0; __thread int local_arena_flags = 0; #if USE_JEMALLOC_EXTENT_HOOKS // Keep track of the size of recently freed memory that might be in the high1g // arena when it is disabled, so that we know when to reenable it. std::atomic_uint g_highArenaRecentlyFreed; alloc::BumpFileMapper* cold_file_mapper = nullptr; // Customized hooks to use 1g pages for jemalloc metadata. static extent_hooks_t huge_page_metadata_hooks; static extent_alloc_t* orig_alloc = nullptr; static bool enableArenaMetadata1GPage = false; static bool enableNumaArenaMetadata1GPage = false; // jemalloc metadata is allocated through the internal base allocator, which // expands memory with an increasingly larger sequence. The default reserved // space (216MB)is a sum of the sequence, from 2MB to 40MB. static size_t a0MetadataReservedSize = 0; static std::atomic<bool> jemallocMetadataCanUseHuge(false); static void* a0ReservedBase = nullptr; static std::atomic<size_t> a0ReservedLeft(0); // Explicit per-thread tcache arenas needing it. // In jemalloc/include/jemalloc/jemalloc_macros.h.in, we have // #define MALLOCX_TCACHE_NONE MALLOCX_TCACHE(-1) __thread int high_arena_tcache = -1; __thread int local_arena_tcache = -1; #endif static unsigned base_arena; #ifdef HAVE_NUMA void set_numa_binding(int node) { if (node < 0) return; // thread not created from JobQueue s_numaNode = node; unsigned arena = base_arena + node; mallctlWrite("thread.arena", arena); if (use_numa) { numa_sched_setaffinity(0, node_to_cpu_mask[node]); numa_set_interleave_mask(numa_no_nodes_ptr); bitmask* nodes = numa_allocate_nodemask(); numa_bitmask_setbit(nodes, node); numa_set_membind(nodes); numa_bitmask_free(nodes); } } void* mallocx_on_node(size_t size, int node, size_t align) { assert((align & (align - 1)) == 0); int flags = MALLOCX_ALIGN(align); if (node < 0) return mallocx(size, flags); int arena = base_arena + node; flags |= MALLOCX_ARENA(arena) | MALLOCX_TCACHE_NONE; return mallocx(size, flags); } #endif // HAVE_NUMA #if USE_JEMALLOC_EXTENT_HOOKS using namespace alloc; static NEVER_INLINE RangeMapper* getMapperChain(RangeState& range, unsigned n1GPages, bool use2MPages, unsigned n2MPages, bool useNormalPages, int numaMask, short nextNode) { RangeMapper* head = nullptr; RangeMapper** ptail = &head; if (n1GPages) { RangeMapper::append(ptail, new Bump1GMapper(range, n1GPages, numaMask, nextNode)); } if (use2MPages) { RangeMapper::append(ptail, new Bump2MMapper(range, n2MPages, numaMask)); } if (useNormalPages) { RangeMapper::append(ptail, new BumpNormalMapper(range, 0, numaMask)); } assertx(head); return head; } // Find the first 2M mapper for the range, and grant it some 2M page budget. // Return the actual number of pages granted. The actual number can be different // from the input, because some part of the range may have already been mapped // in. unsigned allocate2MPagesToRange(AddrRangeClass c, unsigned pages) { auto& range = getRange(c); auto mapper = range.getLowMapper(); if (!mapper) return 0; // Search for the first 2M mapper. do { if (auto mapper2m = dynamic_cast<Bump2MMapper*>(mapper)) { const unsigned maxPages = (range.capacity() - range.mapped()) / size2m; auto const assigned = std::min(pages, maxPages); mapper2m->setMaxPages(assigned); return assigned; } mapper = mapper->next(); } while (mapper); return 0; } void setup_low_arena(PageSpec s) { auto const lowArenaStart = lowArenaMinAddr(); assert(reinterpret_cast<uintptr_t>(sbrk(0)) <= lowArenaStart); always_assert_flog(lowArenaStart <= (2ull << 30), "low arena min addr ({}) must be <= 2GB", lowArenaStart); // Initialize mappers for the VeryLow and Low address ranges. auto& veryLowRange = getRange(AddrRangeClass::VeryLow); auto& lowRange = getRange(AddrRangeClass::Low); auto& emergencyRange = getRange(AddrRangeClass::LowEmergency); auto veryLowMapper = getMapperChain(veryLowRange, (s.n1GPages != 0) ? 1 : 0, true, s.n2MPages, // 2M true, // 4K numa_node_set, 0); auto lowMapper = getMapperChain(lowRange, (s.n1GPages > 1) ? (s.n1GPages - 1) : 0, true, 0, // 2M true, // 4K numa_node_set, 1); auto emergencyMapper = new BumpEmergencyMapper([]{kill(getpid(), SIGTERM);}, emergencyRange); veryLowRange.setLowMapper(veryLowMapper); lowRange.setLowMapper(lowMapper); emergencyRange.setLowMapper(emergencyMapper); auto veryLowColdMapper = new BumpNormalMapper<Direction::HighToLow>(veryLowRange, 0, numa_node_set); auto lowColdMapper = new BumpNormalMapper<Direction::HighToLow>(lowRange, 0, numa_node_set); veryLowRange.setHighMapper(veryLowColdMapper); lowRange.setHighMapper(lowColdMapper); auto ma = LowArena::CreateAt(&g_lowArena); ma->appendMapper(lowMapper); ma->appendMapper(veryLowMapper); ma->appendMapper(emergencyMapper); low_arena = ma->id(); low_arena_flags = MALLOCX_ARENA(low_arena) | MALLOCX_TCACHE_NONE; ma = LowArena::CreateAt(&g_lowerArena); ma->appendMapper(veryLowMapper); ma->appendMapper(lowMapper); ma->appendMapper(emergencyMapper); lower_arena = ma->id(); lower_arena_flags = MALLOCX_ARENA(lower_arena) | MALLOCX_TCACHE_NONE; ma = LowArena::CreateAt(&g_lowColdArena); ma->appendMapper(lowColdMapper); ma->appendMapper(veryLowColdMapper); ma->appendMapper(emergencyMapper); low_cold_arena = ma->id(); low_cold_arena_flags = MALLOCX_ARENA(low_cold_arena) | MALLOCX_TCACHE_NONE; } void setup_high_arena(PageSpec s) { auto& range = getRange(AddrRangeClass::Uncounted); auto mapper = getMapperChain(range, s.n1GPages, true, s.n2MPages, // 2M pages can be added later true, // use normal pages numa_node_set, num_numa_nodes() / 2 + 1); range.setLowMapper(mapper); auto arena = HighArena::CreateAt(&g_highArena); arena->appendMapper(range.getLowMapper()); high_arena = arena->id(); auto& fileRange = getRange(AddrRangeClass::UncountedCold); cold_file_mapper = new BumpFileMapper(fileRange); fileRange.setLowMapper(cold_file_mapper); auto coldMapper = new BumpNormalMapper<Direction::HighToLow>(range, 0, numa_node_set); range.setHighMapper(coldMapper); auto coldArena = HighArena::CreateAt(&g_coldArena); coldArena->appendMapper(cold_file_mapper); coldArena->appendMapper(coldMapper); high_cold_arena = coldArena->id(); high_cold_arena_flags = MALLOCX_ARENA(high_cold_arena) | MALLOCX_TCACHE_NONE; } void setup_arena0(PageSpec s) { size_t size = size1g * s.n1GPages + size2m * s.n2MPages; if (size == 0) return; // Give arena 0 some huge pages, starting at 2TB. auto ret = mmap(reinterpret_cast<void*>(kArena0Base), size + size1g, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE, -1, 0); auto base = reinterpret_cast<uintptr_t>(ret); if (auto r = base % size1g) { // align to 1G boundary base = base + size1g - r; } assertx(base % size1g == 0); auto a0 = PreMappedArena::AttachTo(low_malloc(sizeof(PreMappedArena)), 0, base, base + size, Reserved{}); auto mapper = getMapperChain(*a0, s.n1GPages, s.n2MPages, s.n2MPages, false, numa_node_set, 0); a0->setLowMapper(mapper); g_arena0 = a0; } // Set up extra arenas for use in non-VM threads, when we have short bursts of // worker threads running, e.g., during deserialization of profile data. static std::vector<std::pair<std::vector<DefaultArena*>, std::atomic_uint*>> s_extra_arenas; static unsigned s_extra_arena_per_node; bool setup_extra_arenas(unsigned count) { if (count == 0) return false; // This may be called when we have many other threads running. So hold the // lock while making changes. static std::mutex lock; std::lock_guard<std::mutex> g(lock); // only the first call allocate the arenas. if (!s_extra_arenas.empty()) { return count <= s_extra_arenas.size() * s_extra_arenas[0].first.size(); } // `count` needs to be a multiple of `num_numa_nodes()`, if it isn't, we round // it up to make it easy to balance across nodes. auto const nNodes = std::max(1u, num_numa_nodes()); s_extra_arena_per_node = (count + nNodes - 1) / nNodes; assert(s_extra_arena_per_node >= 1); s_extra_arenas.resize(nNodes); for (unsigned n = 0; n < nNodes; ++n) { s_extra_arenas[n].first.resize(s_extra_arena_per_node); auto constexpr kArenaSize = (sizeof(DefaultArena) + alignof(DefaultArena) - 1) / alignof(DefaultArena) * alignof(DefaultArena); auto const allocSize = kArenaSize * s_extra_arena_per_node + sizeof(std::atomic_uint); void* addr = mallocx_on_node(allocSize, n, alignof(DefaultArena)); memset(addr, 0, allocSize); for (unsigned i = 0; i < s_extra_arena_per_node; ++i) { s_extra_arenas[n].first[i] = DefaultArena::CreateAt(addr); addr = (char*)addr + kArenaSize; } s_extra_arenas[n].second = static_cast<std::atomic_uint*>(addr); } return true; } DefaultArena* next_extra_arena(int node) { if (s_extra_arena_per_node == 0) return nullptr; if (node >= s_extra_arenas.size()) return nullptr; if (node < 0) node = 0; auto const n = static_cast<unsigned>(node); auto counter = s_extra_arenas[n].second; auto const next = counter->fetch_add(1, std::memory_order_relaxed); return s_extra_arenas[n].first[next % s_extra_arena_per_node]; } void* huge_page_extent_alloc(extent_hooks_t* extent_hooks, void* addr, size_t size, size_t alignment, bool* zero, bool* commit, unsigned arena_ind) { // This is used for arena 0's extent_alloc. No malloc / free allowed within // this function since reentrancy is not supported for a0's extent hooks. // Note that, only metadata will use 2M alignment (size will be multiple of 2M // as well). Aligned allocation doesn't require alignment by default, because // of the way virtual memory is expanded with opt.retain (which is the // default). The current extent hook API has no other way to tell if the // allocation is for metadata. The next major jemalloc release will include // this information in the API. if (!jemallocMetadataCanUseHuge.load() || alignment != size2m) { goto default_alloc; } assert(a0ReservedBase != nullptr && (size & (size2m - 1)) == 0); if (arena_ind == 0) { size_t oldValue; while (size <= (oldValue = a0ReservedLeft.load())) { // Try placing a0 metadata on 1G huge pages. if (a0ReservedLeft.compare_exchange_weak(oldValue, oldValue - size)) { assert((oldValue & (size2m - 1)) == 0); return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(a0ReservedBase) + (a0MetadataReservedSize - oldValue)); } } } else if (auto ma = alloc::highArena()) { // For non arena 0: malloc / free allowed in this branch. void* ret = ma->extent_alloc(extent_hooks, addr, size, alignment, zero, commit, high_arena); if (ret != nullptr) return ret; } default_alloc: return orig_alloc(extent_hooks, addr, size, alignment, zero, commit, arena_ind); } /* * Customize arena 0's extent hook to use 1g pages for metadata. */ void setup_jemalloc_metadata_extent_hook(bool enable, bool enable_numa_arena, size_t reserved) { #if !JEMALLOC_METADATA_1G_PAGES return; #endif assert(!jemallocMetadataCanUseHuge.load()); enableArenaMetadata1GPage = enable; enableNumaArenaMetadata1GPage = enable_numa_arena; a0MetadataReservedSize = reserved; auto ma = alloc::highArena(); if (!ma) return; bool retain_enabled = false; mallctlRead("opt.retain", &retain_enabled); if (!enableArenaMetadata1GPage || !retain_enabled) return; bool zero = true, commit = true; void* ret = ma->extent_alloc(nullptr, nullptr, a0MetadataReservedSize, size2m, &zero, &commit, high_arena); if (!ret) return; a0ReservedBase = ret; a0ReservedLeft.store(a0MetadataReservedSize); extent_hooks_t* orig_hooks; int err = mallctlRead<extent_hooks_t*, true>("arena.0.extent_hooks", &orig_hooks); if (err) return; orig_alloc = orig_hooks->alloc; huge_page_metadata_hooks = *orig_hooks; huge_page_metadata_hooks.alloc = &huge_page_extent_alloc; err = mallctlWrite<extent_hooks_t*, true>("arena.0.extent_hooks", &huge_page_metadata_hooks); if (err) return; jemallocMetadataCanUseHuge.store(true); } void arenas_thread_init() { if (high_arena_tcache == -1) { mallctlRead<int, true>("tcache.create", &high_arena_tcache); high_arena_flags = MALLOCX_ARENA(high_arena) | MALLOCX_TCACHE(high_arena_tcache); } if (local_arena_tcache == -1) { local_arena = get_local_arena(s_numaNode); if (local_arena) { mallctlRead<int, true>("tcache.create", &local_arena_tcache); local_arena_flags = MALLOCX_ARENA(local_arena) | MALLOCX_TCACHE(local_arena_tcache); } } if (s_enable_static_arena) { assertx(!tl_static_arena); constexpr size_t kStaticArenaChunkSize = 256 * 1024; static TaggedSlabList s_static_pool; tl_static_arena = new TLStaticArena(kStaticArenaChunkSize, &s_static_pool); } } void arenas_thread_flush() { // It is OK if flushing fails if (high_arena_tcache != -1) { mallctlWrite<int, true>("tcache.flush", high_arena_tcache); } if (local_arena_tcache != -1) { mallctlWrite<int, true>("tcache.flush", local_arena_tcache); } } void arenas_thread_exit() { if (high_arena_tcache != -1) { mallctlWrite<int, true>("tcache.destroy", high_arena_tcache); high_arena_tcache = -1; // Ideally we shouldn't read high_arena_flags any more, but just in case. high_arena_flags = MALLOCX_ARENA(high_arena) | MALLOCX_TCACHE_NONE; } if (local_arena_tcache != -1) { mallctlWrite<int, true>("tcache.destroy", local_arena_tcache); local_arena_tcache = -1; // Ideally we shouldn't read local_arena_flags any more, but just in case. local_arena_flags = MALLOCX_ARENA(local_arena) | MALLOCX_TCACHE_NONE; } if (tl_static_arena) { delete tl_static_arena; tl_static_arena = nullptr; } } #endif // USE_JEMALLOC_EXTENT_HOOKS std::vector<SlabManager*> s_slab_managers; void setup_local_arenas(PageSpec spec, unsigned slabs) { s_slab_managers.reserve(num_numa_nodes()); slabs /= num_numa_nodes(); mallctlRead<unsigned>("arenas.narenas", &base_arena); // throw upon failure // The default one per node. for (int i = 0; i < num_numa_nodes(); i++) { unsigned arena = 0; mallctlRead<unsigned>("arenas.create", &arena); always_assert(arena == base_arena + i); if (slabs) { auto mem = low_malloc(sizeof(SlabManager)); s_slab_managers.push_back(new (mem) SlabManager); } else { s_slab_managers.push_back(nullptr); } } #if USE_JEMALLOC_EXTENT_HOOKS spec.n1GPages = std::min(spec.n1GPages, get_huge1g_info().nr_hugepages); spec.n1GPages /= num_numa_nodes(); spec.n2MPages = std::min(spec.n2MPages, get_huge2m_info().nr_hugepages); spec.n2MPages /= num_numa_nodes(); const size_t reserveSize = spec.n1GPages * size1g + spec.n2MPages * size2m; if (reserveSize == 0) return; g_local_arenas.resize(num_numa_nodes(), 0); for (unsigned i = 0; i < num_numa_nodes(); ++i) { static_assert(kLocalArenaMinAddr % size1g == 0, ""); auto const desiredBase = kLocalArenaMinAddr + i * kLocalArenaSizeLimit; // Try to get the desired address range, but don't use MAP_FIXED. auto ret = mmap(reinterpret_cast<void*>(desiredBase), reserveSize + size1g, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE, -1, 0); if (ret == MAP_FAILED) { throw std::runtime_error{"mmap() failed to reserve address range"}; } auto base = reinterpret_cast<uintptr_t>(ret); if (base % size1g) { // adjust to start at 1GB boundary auto const newBase = (base + size1g - 1) & ~(size1g - 1); munmap(reinterpret_cast<void*>(base), newBase - base); base = newBase; } assert(base % size1g == 0); auto arena = PreMappedArena::CreateAt(low_malloc(sizeof(PreMappedArena)), base, base + reserveSize, Reserved{}); auto mapper = getMapperChain(*arena, spec.n1GPages, (bool)spec.n2MPages, spec.n2MPages, false, // don't use normal pages 1u << i, i); // Allocate some slabs first, which are not given to the arena, but managed // separately by the slab manager. auto const totalSlabSize = std::min(slabs * kSlabSize, reserveSize); if (totalSlabSize) { auto slabRange = mapper->alloc(totalSlabSize, kSlabAlign); if (slabRange) { s_slab_managers[i]->addRange<true>(slabRange, totalSlabSize); } } if (totalSlabSize == reserveSize) continue; arena->setLowMapper(mapper); g_local_arenas[i] = arena; } #endif } unsigned get_local_arena(uint32_t node) { #if USE_JEMALLOC_EXTENT_HOOKS if (node >= g_local_arenas.size()) return 0; auto const arena = g_local_arenas[node]; if (arena == nullptr) return 0; return arena->id(); #else return 0; #endif } SlabManager* get_local_slab_manager(uint32_t node) { if (node >= s_slab_managers.size()) return nullptr; return s_slab_managers[node]; } void shutdown_slab_managers() { for (auto slab_manager : s_slab_managers) { if (slab_manager) slab_manager->shutdown(); } } #endif // USE_JEMALLOC ssize_t get_free_slab_bytes() { ssize_t bytes = 0; #ifdef USE_JEMALLOC for (auto const slabManager : s_slab_managers) { if (slabManager) { bytes += slabManager->bytes(); } } #endif // USE_JEMALLOC return bytes; } struct JEMallocInitializer { JEMallocInitializer() { // The following comes from malloc_extension.cc in google-perftools #ifdef __GLIBC__ // GNU libc++ versions 3.3 and 3.4 obey the environment variables // GLIBCPP_FORCE_NEW and GLIBCXX_FORCE_NEW respectively. Setting // one of these variables forces the STL default allocator to call // new() or delete() for each allocation or deletion. Otherwise // the STL allocator tries to avoid the high cost of doing // allocations by pooling memory internally. setenv("GLIBCPP_FORCE_NEW", "1", false /* no overwrite*/); setenv("GLIBCXX_FORCE_NEW", "1", false /* no overwrite*/); // Now we need to make the setenv 'stick', which it may not do since // the env is flakey before main() is called. But luckily stl only // looks at this env var the first time it tries to do an alloc, and // caches what it finds. So we just cause an stl alloc here. std::string dummy("I need to be allocated"); dummy += "!"; // so the definition of dummy isn't optimized out #endif /* __GLIBC__ */ // Enable backtracing through PHP frames (t9814472). setenv("UNW_RBP_ALWAYS_VALID", "1", false); init_numa(); #ifdef USE_JEMALLOC #if !USE_JEMALLOC_EXTENT_HOOKS // Create the legacy low arena that uses brk() instead of mmap(). When // using newer versions of jemalloc, we use extent hooks to get more // control. If the mallctl fails, it will always_assert in mallctlHelper. if (mallctlRead<unsigned, true>("arenas.create", &low_arena)) { return; } char buf[32]; snprintf(buf, sizeof(buf), "arena.%u.dss", low_arena); if (mallctlWrite<const char*, true>(buf, "primary") != 0) { // Error; bail out. return; } low_arena_flags = MALLOCX_ARENA(low_arena) | MALLOCX_TCACHE_NONE; lower_arena = low_arena; lower_arena_flags = low_arena_flags; low_cold_arena = low_arena; low_cold_arena_flags = low_arena_flags; // We normally maintain the invariant that the region surrounding the // current brk is mapped huge, but we don't know yet whether huge pages // are enabled for low memory. Round up to the start of a huge page, // and set the high water mark to one below. constexpr size_t kHugePageSize = size2m; constexpr size_t kHugePageMask = kHugePageSize - 1; unsigned leftInPage = kHugePageSize - (uintptr_t(sbrk(0)) & kHugePageMask); (void) sbrk(leftInPage); assert((uintptr_t(sbrk(0)) & kHugePageMask) == 0); #else // USE_JEMALLOC_EXTENT_HOOKS unsigned low_1g_pages = 0; if (char* buffer = getenv("HHVM_LOW_1G_PAGE")) { if (!sscanf(buffer, "%u", &low_1g_pages)) { fprintf(stderr, "Bad environment variable HHVM_LOW_1G_PAGE: %s\n", buffer); abort(); } } unsigned high_1g_pages = 0; if (char* buffer = getenv("HHVM_HIGH_1G_PAGE")) { if (!sscanf(buffer, "%u", &high_1g_pages)) { fprintf(stderr, "Bad environment variable HHVM_HIGH_1G_PAGE: %s\n", buffer); abort(); } } unsigned low_2m_pages = 0; if (char* buffer = getenv("HHVM_LOW_2M_PAGE")) { if (!sscanf(buffer, "%u", &low_2m_pages)) { fprintf(stderr, "Bad environment variable HHVM_LOW_2M_PAGE: %s\n", buffer); abort(); } } unsigned high_2m_pages = 0; if (char* buffer = getenv("HHVM_HIGH_2M_PAGE")) { if (!sscanf(buffer, "%u", &high_2m_pages)) { fprintf(stderr, "Bad environment variable HHVM_HIGH_2M_PAGE: %s\n", buffer); abort(); } } HugePageInfo info = get_huge1g_info(); unsigned remaining = static_cast<unsigned>(info.nr_hugepages); if (remaining == 0) { low_1g_pages = high_1g_pages = 0; } else if (low_1g_pages > 0 || high_1g_pages > 0) { KernelVersion version; if (version.m_major < 3 || (version.m_major == 3 && version.m_minor < 9)) { // Older kernels need an explicit hugetlbfs mount point. find_hugetlbfs_path() || auto_mount_hugetlbfs(); } } // Do some allocation between low and high 1G arenas. We use at most 2 1G // pages for the low 1G arena; usually 1 is good enough. auto const origLow1G = low_1g_pages; auto const origHigh1G = high_1g_pages; if (low_1g_pages > 0) { if (low_1g_pages > 2) { low_1g_pages = 2; } if (low_1g_pages + high_1g_pages > remaining) { low_1g_pages = 1; } assert(remaining >= low_1g_pages); remaining -= low_1g_pages; } if (origLow1G) { fprintf(stderr, "using %u (specified %u) 1G huge pages for low arena\n", low_1g_pages, origLow1G); } setup_low_arena({low_1g_pages, low_2m_pages}); if (high_1g_pages > remaining) { high_1g_pages = remaining; } if (origHigh1G) { fprintf(stderr, "using %u (specified %u) 1G huge pages for high arena\n", high_1g_pages, origHigh1G); } setup_high_arena({high_1g_pages, high_2m_pages}); // Make sure high/low arenas are available to the current thread. arenas_thread_init(); #endif // Initialize global mibs init_mallctl_mibs(); #endif } }; #if defined(__GNUC__) && !defined(__APPLE__) // Construct this object before any others. // 101 is the highest priority allowed by the init_priority attribute. // http://gcc.gnu.org/onlinedocs/gcc-4.0.4/gcc/C_002b_002b-Attributes.html #define MAX_CONSTRUCTOR_PRIORITY __attribute__((__init_priority__(101))) #else // init_priority is a gcc extension, so we can't use it on other compilers. // However, since constructor ordering is only known to be an issue with // GNU libc++ we're probably OK on other compilers so let the situation pass // silently instead of issuing a warning. #define MAX_CONSTRUCTOR_PRIORITY #endif static JEMallocInitializer initJEMalloc MAX_CONSTRUCTOR_PRIORITY; void low_2m_pages(uint32_t pages) { #if USE_JEMALLOC_EXTENT_HOOKS pages -= allocate2MPagesToRange(AddrRangeClass::VeryLow, pages); allocate2MPagesToRange(AddrRangeClass::Low, pages); #endif } void high_2m_pages(uint32_t pages) { #if USE_JEMALLOC_EXTENT_HOOKS allocate2MPagesToRange(AddrRangeClass::Uncounted, pages); #endif } void enable_high_cold_file() { #if USE_JEMALLOC_EXTENT_HOOKS if (cold_file_mapper) { cold_file_mapper->enable(); } #endif } void set_cold_file_dir(const char* dir) { #if USE_JEMALLOC_EXTENT_HOOKS if (cold_file_mapper) { cold_file_mapper->setDirectory(dir); } #endif } /////////////////////////////////////////////////////////////////////////////// } extern "C" { const char* malloc_conf = "narenas:1,lg_tcache_max:16" #if (JEMALLOC_VERSION_MAJOR == 5 && JEMALLOC_VERSION_MINOR >= 1) || \ (JEMALLOC_VERSION_MAJOR > 5) // requires jemalloc >= 5.1 ",metadata_thp:disabled" #endif #ifdef ENABLE_HHPROF ",prof:true,prof_active:false,prof_thread_active_init:false" #endif ; }