d038160f3b
Verified with EXTRA_CFLAGS=-Wshadow.
189 lines
5.0 KiB
C
189 lines
5.0 KiB
C
#include "test/jemalloc_test.h"
|
|
|
|
#include "jemalloc/internal/san.h"
|
|
#include "jemalloc/internal/spin.h"
|
|
|
|
static unsigned arena_ind;
|
|
static size_t sz;
|
|
static size_t esz;
|
|
#define NEPOCHS 8
|
|
#define PER_THD_NALLOCS 1
|
|
static atomic_u_t epoch;
|
|
static atomic_u_t nfinished;
|
|
|
|
static unsigned
|
|
do_arena_create(extent_hooks_t *h) {
|
|
unsigned new_arena_ind;
|
|
size_t ind_sz = sizeof(unsigned);
|
|
expect_d_eq(mallctl("arenas.create", (void *)&new_arena_ind, &ind_sz,
|
|
(void *)(h != NULL ? &h : NULL), (h != NULL ? sizeof(h) : 0)), 0,
|
|
"Unexpected mallctl() failure");
|
|
return new_arena_ind;
|
|
}
|
|
|
|
static void
|
|
do_arena_destroy(unsigned ind) {
|
|
size_t mib[3];
|
|
size_t miblen;
|
|
|
|
miblen = sizeof(mib)/sizeof(size_t);
|
|
expect_d_eq(mallctlnametomib("arena.0.destroy", mib, &miblen), 0,
|
|
"Unexpected mallctlnametomib() failure");
|
|
mib[1] = (size_t)ind;
|
|
expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0,
|
|
"Unexpected mallctlbymib() failure");
|
|
}
|
|
|
|
static void
|
|
do_refresh(void) {
|
|
uint64_t refresh_epoch = 1;
|
|
expect_d_eq(mallctl("epoch", NULL, NULL, (void *)&refresh_epoch,
|
|
sizeof(refresh_epoch)), 0, "Unexpected mallctl() failure");
|
|
}
|
|
|
|
static size_t
|
|
do_get_size_impl(const char *cmd, unsigned ind) {
|
|
size_t mib[4];
|
|
size_t miblen = sizeof(mib) / sizeof(size_t);
|
|
size_t z = sizeof(size_t);
|
|
|
|
expect_d_eq(mallctlnametomib(cmd, mib, &miblen),
|
|
0, "Unexpected mallctlnametomib(\"%s\", ...) failure", cmd);
|
|
mib[2] = ind;
|
|
size_t size;
|
|
expect_d_eq(mallctlbymib(mib, miblen, (void *)&size, &z, NULL, 0),
|
|
0, "Unexpected mallctlbymib([\"%s\"], ...) failure", cmd);
|
|
|
|
return size;
|
|
}
|
|
|
|
static size_t
|
|
do_get_active(unsigned ind) {
|
|
return do_get_size_impl("stats.arenas.0.pactive", ind) * PAGE;
|
|
}
|
|
|
|
static size_t
|
|
do_get_mapped(unsigned ind) {
|
|
return do_get_size_impl("stats.arenas.0.mapped", ind);
|
|
}
|
|
|
|
static void *
|
|
thd_start(void *arg) {
|
|
for (unsigned next_epoch = 1; next_epoch < NEPOCHS; next_epoch++) {
|
|
/* Busy-wait for next epoch. */
|
|
unsigned cur_epoch;
|
|
spin_t spinner = SPIN_INITIALIZER;
|
|
while ((cur_epoch = atomic_load_u(&epoch, ATOMIC_ACQUIRE)) !=
|
|
next_epoch) {
|
|
spin_adaptive(&spinner);
|
|
}
|
|
expect_u_eq(cur_epoch, next_epoch, "Unexpected epoch");
|
|
|
|
/*
|
|
* Allocate. The main thread will reset the arena, so there's
|
|
* no need to deallocate.
|
|
*/
|
|
for (unsigned i = 0; i < PER_THD_NALLOCS; i++) {
|
|
void *p = mallocx(sz, MALLOCX_ARENA(arena_ind) |
|
|
MALLOCX_TCACHE_NONE
|
|
);
|
|
expect_ptr_not_null(p,
|
|
"Unexpected mallocx() failure\n");
|
|
}
|
|
|
|
/* Let the main thread know we've finished this iteration. */
|
|
atomic_fetch_add_u(&nfinished, 1, ATOMIC_RELEASE);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
TEST_BEGIN(test_retained) {
|
|
test_skip_if(!config_stats);
|
|
test_skip_if(opt_hpa);
|
|
|
|
arena_ind = do_arena_create(NULL);
|
|
sz = nallocx(HUGEPAGE, 0);
|
|
size_t guard_sz = san_enabled() ? SAN_PAGE_GUARDS_SIZE : 0;
|
|
esz = sz + sz_large_pad + guard_sz;
|
|
|
|
atomic_store_u(&epoch, 0, ATOMIC_RELAXED);
|
|
|
|
unsigned nthreads = ncpus * 2;
|
|
if (LG_SIZEOF_PTR < 3 && nthreads > 16) {
|
|
nthreads = 16; /* 32-bit platform could run out of vaddr. */
|
|
}
|
|
VARIABLE_ARRAY(thd_t, threads, nthreads);
|
|
for (unsigned i = 0; i < nthreads; i++) {
|
|
thd_create(&threads[i], thd_start, NULL);
|
|
}
|
|
|
|
for (unsigned e = 1; e < NEPOCHS; e++) {
|
|
atomic_store_u(&nfinished, 0, ATOMIC_RELEASE);
|
|
atomic_store_u(&epoch, e, ATOMIC_RELEASE);
|
|
|
|
/* Wait for threads to finish allocating. */
|
|
spin_t spinner = SPIN_INITIALIZER;
|
|
while (atomic_load_u(&nfinished, ATOMIC_ACQUIRE) < nthreads) {
|
|
spin_adaptive(&spinner);
|
|
}
|
|
|
|
/*
|
|
* Assert that retained is no more than the sum of size classes
|
|
* that should have been used to satisfy the worker threads'
|
|
* requests, discounting per growth fragmentation.
|
|
*/
|
|
do_refresh();
|
|
|
|
size_t allocated = (esz - guard_sz) * nthreads *
|
|
PER_THD_NALLOCS;
|
|
size_t active = do_get_active(arena_ind);
|
|
expect_zu_le(allocated, active, "Unexpected active memory");
|
|
size_t mapped = do_get_mapped(arena_ind);
|
|
expect_zu_le(active, mapped, "Unexpected mapped memory");
|
|
|
|
arena_t *arena = arena_get(tsdn_fetch(), arena_ind, false);
|
|
size_t usable = 0;
|
|
size_t fragmented = 0;
|
|
for (pszind_t pind = sz_psz2ind(HUGEPAGE); pind <
|
|
arena->pa_shard.pac.exp_grow.next; pind++) {
|
|
size_t psz = sz_pind2sz(pind);
|
|
size_t psz_fragmented = psz % esz;
|
|
size_t psz_usable = psz - psz_fragmented;
|
|
/*
|
|
* Only consider size classes that wouldn't be skipped.
|
|
*/
|
|
if (psz_usable > 0) {
|
|
expect_zu_lt(usable, allocated,
|
|
"Excessive retained memory "
|
|
"(%#zx[+%#zx] > %#zx)", usable, psz_usable,
|
|
allocated);
|
|
fragmented += psz_fragmented;
|
|
usable += psz_usable;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Clean up arena. Destroying and recreating the arena
|
|
* is simpler that specifying extent hooks that deallocate
|
|
* (rather than retaining) during reset.
|
|
*/
|
|
do_arena_destroy(arena_ind);
|
|
expect_u_eq(do_arena_create(NULL), arena_ind,
|
|
"Unexpected arena index");
|
|
}
|
|
|
|
for (unsigned i = 0; i < nthreads; i++) {
|
|
thd_join(threads[i], NULL);
|
|
}
|
|
|
|
do_arena_destroy(arena_ind);
|
|
}
|
|
TEST_END
|
|
|
|
int
|
|
main(void) {
|
|
return test(
|
|
test_retained);
|
|
}
|