Commit 4cabbe62 authored by Gisle Dankel's avatar Gisle Dankel Committed by Facebook Github Bot

Fix memory usage issue

Summary:
I noticed during testing that jemalloc requests a lot more memory than is
allocated by the application.
This is particularly the case for allocations >4k. If the allocation is <2MB it
asks for an extra 2MB region that is not used for allocated memory, and if the
request is >2MB then an extra region of the same size is requested in the
extent hook.
Turns out the issue was that I didn't set *commit to true in the alloc hook,
causing an interesting sequence of events to occur.
Another issue is that when running out of space and falling back to the default
hook, subsequent requests would increase in size forever. The solution here was
to set the jemalloc retain grow limit.
Finally, fixed a bug when reserving more than 4GB memory (size calculation was
truncated to 32 bits).

Reviewed By: interwq

Differential Revision: D10316308

fbshipit-source-id: af47438029e85c03bf51c8d132b88de551f493f1
parent 3c69262c
......@@ -94,7 +94,7 @@ class HugePageArena {
extent_hooks_t extentHooks_;
};
constexpr int kHugePageSize = 2 * 1024 * 1024;
constexpr size_t kHugePageSize = 2 * 1024 * 1024;
// Singleton arena instance
static HugePageArena arena;
......@@ -146,12 +146,13 @@ void* HugePageArena::allocHook(
bool* zero,
bool* commit,
unsigned arena_ind) {
VLOG(1) << "Extent request of size " << size;
DCHECK((size & (size - 1)) == 0);
void* res = nullptr;
if (new_addr == nullptr) {
res = arena.reserve(size, alignment);
}
LOG(INFO) << "Extent request of size " << size << " alignment " << alignment
<< " = " << res << " (" << arena.freeSpace() << " bytes free)";
if (res == nullptr) {
LOG_IF(WARNING, new_addr != nullptr) << "Explicit address not supported";
res = arena.originalAlloc_(
......@@ -160,6 +161,7 @@ void* HugePageArena::allocHook(
if (*zero) {
bzero(res, size);
}
*commit = true;
}
return res;
}
......@@ -168,9 +170,6 @@ int HugePageArena::init(int nr_pages) {
DCHECK(start_ == 0);
DCHECK(usingJEMalloc());
// Allocate one extra page for jemalloc's internal use
nr_pages++;
unsigned arena_index;
size_t len = sizeof(arena_index);
if (auto ret = mallctl("arenas.create", &arena_index, &len, nullptr, 0)) {
......@@ -178,12 +177,40 @@ int HugePageArena::init(int nr_pages) {
return 0;
}
std::ostringstream key;
key << "arena." << arena_index << ".extent_hooks";
// Set grow retained limit to stop jemalloc from
// forever increasing the requested size after failed allocations.
// Normally jemalloc asks for maps of increasing size in order to avoid
// hitting the limit of allowed mmaps per process.
// Since this arena is backed by a single mmap and is using huge pages,
// this is not a concern here.
// TODO: Support growth of the huge page arena.
size_t mib[3];
size_t miblen = sizeof(mib) / sizeof(size_t);
std::ostringstream rtl_key;
rtl_key << "arena." << arena_index << ".retain_grow_limit";
if (auto ret = mallctlnametomib(rtl_key.str().c_str(), mib, &miblen)) {
print_error(ret, "Unable to read growth limit");
return 0;
}
size_t grow_retained_limit = kHugePageSize;
mib[1] = arena_index;
if (auto ret = mallctlbymib(
mib,
miblen,
nullptr,
nullptr,
&grow_retained_limit,
sizeof(grow_retained_limit))) {
print_error(ret, "Unable to set growth limit");
return 0;
}
std::ostringstream hooks_key;
hooks_key << "arena." << arena_index << ".extent_hooks";
extent_hooks_t* hooks;
len = sizeof(hooks);
// Read the existing hooks
if (auto ret = mallctl(key.str().c_str(), &hooks, &len, nullptr, 0)) {
if (auto ret = mallctl(hooks_key.str().c_str(), &hooks, &len, nullptr, 0)) {
print_error(ret, "Unable to get the hooks");
return 0;
}
......@@ -194,7 +221,11 @@ int HugePageArena::init(int nr_pages) {
extentHooks_.alloc = &allocHook;
extent_hooks_t* new_hooks = &extentHooks_;
if (auto ret = mallctl(
key.str().c_str(), nullptr, nullptr, &new_hooks, sizeof(new_hooks))) {
hooks_key.str().c_str(),
nullptr,
nullptr,
&new_hooks,
sizeof(new_hooks))) {
print_error(ret, "Unable to set the hooks");
return 0;
}
......
......@@ -24,11 +24,19 @@
using jha = folly::JemallocHugePageAllocator;
static constexpr int kb(int kilos) {
return kilos * 1024;
}
static constexpr int mb(int megs) {
return kb(megs * 1024);
}
TEST(JemallocHugePageAllocatorTest, Basic) {
EXPECT_FALSE(jha::initialized());
// Allocation should work even if uninitialized
auto ptr = jha::allocate(1024);
auto ptr = jha::allocate(kb(1));
EXPECT_NE(nullptr, ptr);
jha::deallocate(ptr);
......@@ -37,7 +45,7 @@ TEST(JemallocHugePageAllocatorTest, Basic) {
EXPECT_NE(0, jha::freeSpace());
}
ptr = jha::allocate(1024);
ptr = jha::allocate(kb(1));
EXPECT_NE(nullptr, ptr);
if (initialized) {
......@@ -58,7 +66,7 @@ TEST(JemallocHugePageAllocatorTest, Basic) {
TEST(JemallocHugePageAllocatorTest, LargeAllocations) {
// Allocate before init - will not use huge pages
void* ptr0 = jha::allocate(3 * 1024 * 512);
void* ptr0 = jha::allocate(kb(1));
// One 2MB huge page
bool initialized = jha::init(1);
......@@ -67,7 +75,7 @@ TEST(JemallocHugePageAllocatorTest, LargeAllocations) {
}
// This fits
void* ptr1 = jha::allocate(3 * 1024 * 512);
void* ptr1 = jha::allocate(mb(1) + kb(512));
EXPECT_NE(nullptr, ptr1);
if (initialized) {
......@@ -75,7 +83,7 @@ TEST(JemallocHugePageAllocatorTest, LargeAllocations) {
}
// This is too large to fit
void* ptr2 = jha::allocate(4 * 1024 * 1024);
void* ptr2 = jha::allocate(mb(1));
EXPECT_NE(nullptr, ptr2);
EXPECT_FALSE(jha::addressInArena(ptr2));
......@@ -83,31 +91,146 @@ TEST(JemallocHugePageAllocatorTest, LargeAllocations) {
// Free and reuse huge page area
jha::deallocate(ptr2);
jha::deallocate(ptr0);
ptr2 = jha::allocate(1024 * 1024);
ptr2 = jha::allocate(mb(1));
// No memory in the huge page arena was freed - ptr0 was allocated
// before init and ptr2 didn't fit
EXPECT_FALSE(jha::addressInArena(ptr2));
jha::deallocate(ptr1);
ptr1 = jha::allocate(3 * 1024 * 512);
EXPECT_NE(nullptr, ptr1);
void* ptr3 = jha::allocate(mb(1) + kb(512));
EXPECT_NE(nullptr, ptr3);
if (initialized) {
EXPECT_TRUE(jha::addressInArena(ptr1));
EXPECT_EQ(ptr1, ptr3);
EXPECT_TRUE(jha::addressInArena(ptr3));
}
// Just using free works equally well
free(ptr1);
ptr1 = jha::allocate(3 * 1024 * 512);
EXPECT_NE(nullptr, ptr1);
free(ptr3);
ptr3 = jha::allocate(mb(1) + kb(512));
EXPECT_NE(nullptr, ptr3);
if (initialized) {
EXPECT_TRUE(jha::addressInArena(ptr1));
EXPECT_TRUE(jha::addressInArena(ptr3));
}
jha::deallocate(ptr2);
jha::deallocate(ptr3);
}
TEST(JemallocHugePageAllocatorTest, MemoryUsageTest) {
bool initialized = jha::init(80);
if (initialized) {
EXPECT_GE(jha::freeSpace(), mb(160));
}
struct c32 {
char val[32];
};
using Vec32 = std::vector<c32, folly::CxxHugePageAllocator<c32>>;
Vec32 vec32;
for (int i = 0; i < 10; i++) {
vec32.push_back({});
}
void* ptr1 = jha::allocate(32);
if (initialized) {
EXPECT_GE(jha::freeSpace(), mb(158));
}
struct c320 {
char val[320];
};
using Vec320 = std::vector<c320, folly::CxxHugePageAllocator<c320>>;
Vec320 vec320;
for (int i = 0; i < 10; i++) {
vec320.push_back({});
}
void* ptr2 = jha::allocate(320);
if (initialized) {
EXPECT_GE(jha::freeSpace(), mb(158));
}
// Helper to ensure all allocations are freed at the end
auto deleter = [](void* data) { jha::deallocate(data); };
std::vector<std::unique_ptr<void, decltype(deleter)>> ptr_vec;
auto alloc = [&ptr_vec, &deleter](size_t size) {
ptr_vec.emplace_back(jha::allocate(size), deleter);
};
for (int i = 0; i < 10; i++) {
alloc(kb(1));
}
void* ptr3 = jha::allocate(kb(1));
if (initialized) {
EXPECT_GE(jha::freeSpace(), mb(158));
}
for (int i = 0; i < 10; i++) {
alloc(kb(4));
}
void* ptr4 = jha::allocate(kb(4));
if (initialized) {
EXPECT_GE(jha::freeSpace(), mb(158));
}
for (int i = 0; i < 10; i++) {
alloc(kb(10));
}
void* ptr5 = jha::allocate(kb(10));
if (initialized) {
EXPECT_GE(jha::freeSpace(), mb(158));
}
alloc(kb(512));
alloc(mb(1));
void* ptr6 = jha::allocate(mb(1));
if (initialized) {
EXPECT_GE(jha::freeSpace(), mb(156));
}
alloc(mb(2));
alloc(mb(4));
void* ptr7 = jha::allocate(mb(4));
if (initialized) {
EXPECT_GE(jha::freeSpace(), mb(146));
}
alloc(kb(512));
alloc(kb(512));
if (initialized) {
EXPECT_GE(jha::freeSpace(), 145);
}
void* ptr8 = jha::allocate(mb(64));
if (initialized) {
EXPECT_GE(jha::freeSpace(), mb(80));
}
alloc(mb(64));
if (initialized) {
EXPECT_GE(jha::freeSpace(), mb(16));
}
alloc(mb(256));
alloc(mb(256));
alloc(mb(256));
// Now free a bunch of objects and then reallocate
// the same size objects again.
// This should not result in usage of free space.
size_t free = jha::freeSpace();
jha::deallocate(ptr1);
jha::deallocate(ptr2);
jha::deallocate(ptr3);
jha::deallocate(ptr4);
jha::deallocate(ptr5);
jha::deallocate(ptr6);
jha::deallocate(ptr7);
jha::deallocate(ptr8);
alloc(32);
alloc(320);
alloc(kb(1));
alloc(kb(4));
alloc(kb(10));
alloc(mb(1));
alloc(mb(4));
alloc(mb(64));
if (initialized) {
EXPECT_EQ(free, jha::freeSpace());
}
}
TEST(JemallocHugePageAllocatorTest, STLAllocator) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment