Mark partially purged arena chunks as non-hugepage.

Add the pages_[no]huge() functions, which toggle huge page state via
madvise(..., MADV_[NO]HUGEPAGE) calls.

The first time a page run is purged from within an arena chunk, call
pages_nohuge() to tell the kernel to make no further attempts to back
the chunk with huge pages.  Upon arena chunk deletion, restore the
associated virtual memory to its original state via pages_huge().

This resolves #243.
This commit is contained in:
Jason Evans 2016-11-17 13:36:17 -08:00
parent fc11f3cb84
commit e98a620c59
9 changed files with 110 additions and 5 deletions

View File

@ -167,6 +167,7 @@ TESTS_UNIT := \
$(srcroot)test/unit/mq.c \ $(srcroot)test/unit/mq.c \
$(srcroot)test/unit/mtx.c \ $(srcroot)test/unit/mtx.c \
$(srcroot)test/unit/pack.c \ $(srcroot)test/unit/pack.c \
$(srcroot)test/unit/pages.c \
$(srcroot)test/unit/ph.c \ $(srcroot)test/unit/ph.c \
$(srcroot)test/unit/prng.c \ $(srcroot)test/unit/prng.c \
$(srcroot)test/unit/prof_accum.c \ $(srcroot)test/unit/prof_accum.c \

View File

@ -1612,6 +1612,8 @@ JE_COMPILABLE([madvise(2)], [
madvise((void *)0, 0, 0); madvise((void *)0, 0, 0);
], [je_cv_madvise]) ], [je_cv_madvise])
if test "x${je_cv_madvise}" = "xyes" ; then if test "x${je_cv_madvise}" = "xyes" ; then
AC_DEFINE([JEMALLOC_HAVE_MADVISE], [ ])
dnl Check for madvise(..., MADV_FREE). dnl Check for madvise(..., MADV_FREE).
JE_COMPILABLE([madvise(..., MADV_FREE)], [ JE_COMPILABLE([madvise(..., MADV_FREE)], [
#include <sys/mman.h> #include <sys/mman.h>
@ -1632,9 +1634,15 @@ if test "x${je_cv_madvise}" = "xyes" ; then
AC_DEFINE([JEMALLOC_PURGE_MADVISE_DONTNEED], [ ]) AC_DEFINE([JEMALLOC_PURGE_MADVISE_DONTNEED], [ ])
fi fi
if test "x${je_cv_madv_free}" = "xyes" \ dnl Check for madvise(..., MADV_[NO]HUGEPAGE).
-o "x${je_cv_madv_dontneed}" = "xyes" ; then JE_COMPILABLE([madvise(..., MADV_[[NO]]HUGEPAGE)], [
AC_DEFINE([JEMALLOC_HAVE_MADVISE], [ ]) #include <sys/mman.h>
], [
madvise((void *)0, 0, MADV_HUGEPAGE);
madvise((void *)0, 0, MADV_NOHUGEPAGE);
], [je_cv_thp])
if test "x${je_cv_thp}" = "xyes" ; then
AC_DEFINE([JEMALLOC_THP], [ ])
fi fi
fi fi

View File

@ -190,6 +190,14 @@ struct arena_chunk_s {
*/ */
extent_node_t node; extent_node_t node;
/*
* True if memory could be backed by transparent huge pages. This is
* only directly relevant to Linux, since it is the only supported
* platform on which jemalloc interacts with explicit transparent huge
* page controls.
*/
bool hugepage;
/* /*
* Map of pages within chunk that keeps track of free/large/small. The * Map of pages within chunk that keeps track of free/large/small. The
* first map_bias entries are omitted, since the chunk header does not * first map_bias entries are omitted, since the chunk header does not

View File

@ -265,6 +265,12 @@
#undef JEMALLOC_PURGE_MADVISE_FREE #undef JEMALLOC_PURGE_MADVISE_FREE
#undef JEMALLOC_PURGE_MADVISE_DONTNEED #undef JEMALLOC_PURGE_MADVISE_DONTNEED
/*
* Defined if transparent huge pages are supported via the MADV_[NO]HUGEPAGE
* arguments to madvise(2).
*/
#undef JEMALLOC_THP
/* Define if operating system has alloca.h header. */ /* Define if operating system has alloca.h header. */
#undef JEMALLOC_HAS_ALLOCA_H #undef JEMALLOC_HAS_ALLOCA_H

View File

@ -16,6 +16,8 @@ void *pages_trim(void *addr, size_t alloc_size, size_t leadsize,
bool pages_commit(void *addr, size_t size); bool pages_commit(void *addr, size_t size);
bool pages_decommit(void *addr, size_t size); bool pages_decommit(void *addr, size_t size);
bool pages_purge(void *addr, size_t size); bool pages_purge(void *addr, size_t size);
bool pages_huge(void *addr, size_t size);
bool pages_nohuge(void *addr, size_t size);
void pages_boot(void); void pages_boot(void);
#endif /* JEMALLOC_H_EXTERNS */ #endif /* JEMALLOC_H_EXTERNS */

View File

@ -397,7 +397,9 @@ p2rz
pages_boot pages_boot
pages_commit pages_commit
pages_decommit pages_decommit
pages_huge
pages_map pages_map
pages_nohuge
pages_purge pages_purge
pages_trim pages_trim
pages_unmap pages_unmap

View File

@ -664,6 +664,8 @@ arena_chunk_init_hard(tsdn_t *tsdn, arena_t *arena)
if (chunk == NULL) if (chunk == NULL)
return (NULL); return (NULL);
chunk->hugepage = true;
/* /*
* Initialize the map to contain one maximal free untouched run. Mark * Initialize the map to contain one maximal free untouched run. Mark
* the pages as zeroed if arena_chunk_alloc_internal() returned a zeroed * the pages as zeroed if arena_chunk_alloc_internal() returned a zeroed
@ -727,13 +729,14 @@ arena_chunk_alloc(tsdn_t *tsdn, arena_t *arena)
static void static void
arena_chunk_discard(tsdn_t *tsdn, arena_t *arena, arena_chunk_t *chunk) arena_chunk_discard(tsdn_t *tsdn, arena_t *arena, arena_chunk_t *chunk)
{ {
size_t sn; size_t sn, hugepage;
bool committed; bool committed;
chunk_hooks_t chunk_hooks = CHUNK_HOOKS_INITIALIZER; chunk_hooks_t chunk_hooks = CHUNK_HOOKS_INITIALIZER;
chunk_deregister(chunk, &chunk->node); chunk_deregister(chunk, &chunk->node);
sn = extent_node_sn_get(&chunk->node); sn = extent_node_sn_get(&chunk->node);
hugepage = chunk->hugepage;
committed = (arena_mapbits_decommitted_get(chunk, map_bias) == 0); committed = (arena_mapbits_decommitted_get(chunk, map_bias) == 0);
if (!committed) { if (!committed) {
/* /*
@ -746,6 +749,14 @@ arena_chunk_discard(tsdn_t *tsdn, arena_t *arena, arena_chunk_t *chunk)
chunk_hooks.decommit(chunk, chunksize, 0, map_bias << LG_PAGE, chunk_hooks.decommit(chunk, chunksize, 0, map_bias << LG_PAGE,
arena->ind); arena->ind);
} }
if (!hugepage) {
/*
* Convert chunk back to the default state, so that all
* subsequent chunk allocations start out with chunks that can
* be backed by transparent huge pages.
*/
pages_huge(chunk, chunksize);
}
chunk_dalloc_cache(tsdn, arena, &chunk_hooks, (void *)chunk, chunksize, chunk_dalloc_cache(tsdn, arena, &chunk_hooks, (void *)chunk, chunksize,
sn, committed); sn, committed);
@ -1682,6 +1693,17 @@ arena_purge_stashed(tsdn_t *tsdn, arena_t *arena, chunk_hooks_t *chunk_hooks,
run_size = arena_mapbits_large_size_get(chunk, pageind); run_size = arena_mapbits_large_size_get(chunk, pageind);
npages = run_size >> LG_PAGE; npages = run_size >> LG_PAGE;
/*
* If this is the first run purged within chunk, mark
* the chunk as non-huge. This will prevent all use of
* transparent huge pages for this chunk until the chunk
* as a whole is deallocated.
*/
if (chunk->hugepage) {
pages_nohuge(chunk, chunksize);
chunk->hugepage = false;
}
assert(pageind + npages <= chunk_npages); assert(pageind + npages <= chunk_npages);
assert(!arena_mapbits_decommitted_get(chunk, pageind)); assert(!arena_mapbits_decommitted_get(chunk, pageind));
assert(!arena_mapbits_decommitted_get(chunk, assert(!arena_mapbits_decommitted_get(chunk,

View File

@ -170,7 +170,8 @@ pages_purge(void *addr, size_t size)
#ifdef _WIN32 #ifdef _WIN32
VirtualAlloc(addr, size, MEM_RESET, PAGE_READWRITE); VirtualAlloc(addr, size, MEM_RESET, PAGE_READWRITE);
unzeroed = true; unzeroed = true;
#elif defined(JEMALLOC_HAVE_MADVISE) #elif (defined(JEMALLOC_PURGE_MADVISE_FREE) || \
defined(JEMALLOC_PURGE_MADVISE_FREE))
# if defined(JEMALLOC_PURGE_MADVISE_FREE) # if defined(JEMALLOC_PURGE_MADVISE_FREE)
# define JEMALLOC_MADV_PURGE MADV_FREE # define JEMALLOC_MADV_PURGE MADV_FREE
# define JEMALLOC_MADV_ZEROS false # define JEMALLOC_MADV_ZEROS false
@ -191,6 +192,34 @@ pages_purge(void *addr, size_t size)
return (unzeroed); return (unzeroed);
} }
bool
pages_huge(void *addr, size_t size)
{
assert(PAGE_ADDR2BASE(addr) == addr);
assert(PAGE_CEILING(size) == size);
#ifdef JEMALLOC_THP
return (madvise(addr, size, MADV_HUGEPAGE) != 0);
#else
return (false);
#endif
}
bool
pages_nohuge(void *addr, size_t size)
{
assert(PAGE_ADDR2BASE(addr) == addr);
assert(PAGE_CEILING(size) == size);
#ifdef JEMALLOC_THP
return (madvise(addr, size, MADV_NOHUGEPAGE) != 0);
#else
return (false);
#endif
}
#ifdef JEMALLOC_SYSCTL_VM_OVERCOMMIT #ifdef JEMALLOC_SYSCTL_VM_OVERCOMMIT
static bool static bool
os_overcommits_sysctl(void) os_overcommits_sysctl(void)

27
test/unit/pages.c Normal file
View File

@ -0,0 +1,27 @@
#include "test/jemalloc_test.h"
TEST_BEGIN(test_pages_huge)
{
bool commit;
void *pages;
commit = true;
pages = pages_map(NULL, PAGE, &commit);
assert_ptr_not_null(pages, "Unexpected pages_map() error");
assert_false(pages_huge(pages, PAGE),
"Unexpected pages_huge() result");
assert_false(pages_nohuge(pages, PAGE),
"Unexpected pages_nohuge() result");
pages_unmap(pages, PAGE);
}
TEST_END
int
main(void)
{
return (test(
test_pages_huge));
}