summaryrefslogtreecommitdiffstats
path: root/lib/libc/stdlib/malloc.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libc/stdlib/malloc.c')
-rw-r--r--lib/libc/stdlib/malloc.c649
1 files changed, 219 insertions, 430 deletions
diff --git a/lib/libc/stdlib/malloc.c b/lib/libc/stdlib/malloc.c
index 014d8c0..ced8511 100644
--- a/lib/libc/stdlib/malloc.c
+++ b/lib/libc/stdlib/malloc.c
@@ -38,10 +38,9 @@
* + Cache line sharing between arenas is avoided for internal data
* structures.
*
- * + Memory is managed in chunks and runs (chunks can be split into runs using
- * a binary buddy scheme), rather than as individual pages. This provides
- * a constant-time mechanism for associating allocations with particular
- * arenas.
+ * + Memory is managed in chunks and runs (chunks can be split into runs),
+ * rather than as individual pages. This provides a constant-time
+ * mechanism for associating allocations with particular arenas.
*
* Allocation requests are rounded up to the nearest size class, and no record
* of the original request size is maintained. Allocations are broken into
@@ -94,85 +93,6 @@
*/
/*
- *******************************************************************************
- *
- * Ring macros.
- *
- *******************************************************************************
- */
-
-/* Ring definitions. */
-#define qr(a_type) struct { \
- a_type *qre_next; \
- a_type *qre_prev; \
-}
-
-#define qr_initializer {NULL, NULL}
-
-/* Ring functions. */
-#define qr_new(a_qr, a_field) do { \
- (a_qr)->a_field.qre_next = (a_qr); \
- (a_qr)->a_field.qre_prev = (a_qr); \
-} while (0)
-
-#define qr_next(a_qr, a_field) ((a_qr)->a_field.qre_next)
-
-#define qr_prev(a_qr, a_field) ((a_qr)->a_field.qre_prev)
-
-#define qr_before_insert(a_qrelm, a_qr, a_field) do { \
- (a_qr)->a_field.qre_prev = (a_qrelm)->a_field.qre_prev; \
- (a_qr)->a_field.qre_next = (a_qrelm); \
- (a_qr)->a_field.qre_prev->a_field.qre_next = (a_qr); \
- (a_qrelm)->a_field.qre_prev = (a_qr); \
-} while (0)
-
-#define qr_after_insert(a_qrelm, a_qr, a_field) do { \
- (a_qr)->a_field.qre_next = (a_qrelm)->a_field.qre_next; \
- (a_qr)->a_field.qre_prev = (a_qrelm); \
- (a_qr)->a_field.qre_next->a_field.qre_prev = (a_qr); \
- (a_qrelm)->a_field.qre_next = (a_qr); \
-} while (0)
-
-#define qr_meld(a_qr_a, a_qr_b, a_type, a_field) do { \
- a_type *t; \
- (a_qr_a)->a_field.qre_prev->a_field.qre_next = (a_qr_b); \
- (a_qr_b)->a_field.qre_prev->a_field.qre_next = (a_qr_a); \
- t = (a_qr_a)->a_field.qre_prev; \
- (a_qr_a)->a_field.qre_prev = (a_qr_b)->a_field.qre_prev; \
- (a_qr_b)->a_field.qre_prev = t; \
-} while (0)
-
-/*
- * qr_meld() and qr_split() are functionally equivalent, so there's no need to
- * have two copies of the code.
- */
-#define qr_split(a_qr_a, a_qr_b, a_type, a_field) \
- qr_meld((a_qr_a), (a_qr_b), a_type, a_field)
-
-#define qr_remove(a_qr, a_field) do { \
- (a_qr)->a_field.qre_prev->a_field.qre_next \
- = (a_qr)->a_field.qre_next; \
- (a_qr)->a_field.qre_next->a_field.qre_prev \
- = (a_qr)->a_field.qre_prev; \
- (a_qr)->a_field.qre_next = (a_qr); \
- (a_qr)->a_field.qre_prev = (a_qr); \
-} while (0)
-
-#define qr_foreach(var, a_qr, a_field) \
- for ((var) = (a_qr); \
- (var) != NULL; \
- (var) = (((var)->a_field.qre_next != (a_qr)) \
- ? (var)->a_field.qre_next : NULL))
-
-#define qr_reverse_foreach(var, a_qr, a_field) \
- for ((var) = ((a_qr) != NULL) ? qr_prev(a_qr, a_field) : NULL; \
- (var) != NULL; \
- (var) = (((var) != (a_qr)) \
- ? (var)->a_field.qre_prev : NULL))
-
-/******************************************************************************/
-
-/*
* MALLOC_PRODUCTION disables assertions and statistics gathering. It also
* defaults the A and J runtime options to off. These settings are appropriate
* for production systems.
@@ -331,7 +251,7 @@ __FBSDID("$FreeBSD$");
#define RUN_MAX_OVRHD_RELAX 1.5
/* Put a cap on small object run size. This overrides RUN_MAX_OVRHD. */
-#define RUN_MAX_SMALL_2POW 16
+#define RUN_MAX_SMALL_2POW 15
#define RUN_MAX_SMALL (1 << RUN_MAX_SMALL_2POW)
/******************************************************************************/
@@ -369,10 +289,10 @@ struct malloc_bin_stats_s {
uint64_t nruns;
/*
- * Total number of run promotions/demotions for this bin's size class.
+ * Total number of runs reused by extracting them from the runs tree for
+ * this bin's size class.
*/
- uint64_t npromote;
- uint64_t ndemote;
+ uint64_t reruns;
/* High-water mark for this bin. */
unsigned long highruns;
@@ -505,8 +425,8 @@ RB_HEAD(arena_chunk_tree_s, arena_chunk_s);
typedef struct arena_run_s arena_run_t;
struct arena_run_s {
- /* Linkage for run rings. */
- qr(arena_run_t) link;
+ /* Linkage for run trees. */
+ RB_ENTRY(arena_run_s) link;
#ifdef MALLOC_DEBUG
uint32_t magic;
@@ -522,44 +442,11 @@ struct arena_run_s {
/* Number of free regions in run. */
unsigned nfree;
- /*
- * Current quartile for this run, one of: {RUN_QINIT, RUN_Q0, RUN_25,
- * RUN_Q50, RUN_Q75, RUN_Q100}.
- */
-#define RUN_QINIT 0
-#define RUN_Q0 1
-#define RUN_Q25 2
-#define RUN_Q50 3
-#define RUN_Q75 4
-#define RUN_Q100 5
- unsigned quartile;
-
- /*
- * Limits on the number of free regions for the fullness quartile this
- * run is currently in. If nfree goes outside these limits, the run
- * is moved to a different fullness quartile.
- */
- unsigned free_max;
- unsigned free_min;
-
/* Bitmask of in-use regions (0: in use, 1: free). */
unsigned regs_mask[1]; /* Dynamically sized. */
};
-
-/* Used for run ring headers, where the run isn't actually used. */
-typedef struct arena_run_link_s arena_run_link_t;
-struct arena_run_link_s {
- /* Linkage for run rings. */
- qr(arena_run_t) link;
-};
-
-/* Avoid pointer aliasing issues. */
-static inline arena_run_t *
-arena_bin_link(void *ptr)
-{
-
- return ((arena_run_t *)ptr);
-}
+typedef struct arena_run_tree_s arena_run_tree_t;
+RB_HEAD(arena_run_tree_s, arena_run_s);
struct arena_bin_s {
/*
@@ -569,38 +456,13 @@ struct arena_bin_s {
arena_run_t *runcur;
/*
- * Links into rings of runs, of various fullnesses (names indicate
- * approximate lower bounds). A new run conceptually starts off in
- * runsinit, and it isn't inserted into the runs0 ring until it
- * reaches 25% full (hysteresis mechanism). For the run to be moved
- * again, it must become either empty or 50% full. Thus, each ring
- * contains runs that are within 50% above the advertised fullness for
- * the ring. This provides a low-overhead mechanism for segregating
- * runs into approximate fullness classes.
- *
- * Conceptually, there is a runs100 that contains completely full runs.
- * Since we don't need to search for these runs though, no runs100 ring
- * is actually maintained.
- *
- * These rings are useful when looking for an existing run to use when
- * runcur is no longer usable. We look for usable runs in the
- * following order:
- *
- * 1) runs50
- * 2) runs25
- * 3) runs0
- * 4) runs75
- *
- * runs75 isn't a good place to look, because it contains runs that may
- * be nearly completely full. Still, we look there as a last resort in
- * order to avoid allocating a new run if at all possible.
+ * Tree of non-full runs. This tree is used when looking for an
+ * existing run when runcur is no longer usable. We choose the
+ * non-full run that is lowest in memory; this policy tends to keep
+ * objects packed well, and it can also help reduce the number of
+ * almost-empty chunks.
*/
- /* arena_run_link_t runsinit; 0% <= fullness < 25% */
- arena_run_link_t runs0; /* 0% < fullness < 50% */
- arena_run_link_t runs25; /* 25% < fullness < 75% */
- arena_run_link_t runs50; /* 50% < fullness < 100% */
- arena_run_link_t runs75; /* 75% < fullness < 100% */
- /* arena_run_link_t runs100; fullness == 100% */
+ arena_run_tree_t runs;
/* Size of regions in a run for this bin's size class. */
size_t reg_size;
@@ -727,7 +589,7 @@ static chunk_tree_t huge;
*/
/*
* Protects sbrk() calls. This must be separate from chunks_mtx, since
- * base_chunk_alloc() also uses sbrk(), but cannot lock chunks_mtx (doing so
+ * base_pages_alloc() also uses sbrk(), but cannot lock chunks_mtx (doing so
* could cause recursive lock acquisition).
*/
static malloc_mutex_t brk_mtx;
@@ -740,10 +602,7 @@ static void *brk_max;
#endif
#ifdef MALLOC_STATS
-/*
- * Byte counters for allocated/mapped space used by the chunks in the huge
- * allocations tree.
- */
+/* Huge allocation statistics. */
static uint64_t huge_nmalloc;
static uint64_t huge_ndalloc;
static size_t huge_allocated;
@@ -761,13 +620,13 @@ static chunk_tree_t old_chunks;
*/
/*
- * Current chunk that is being used for internal memory allocations. This
- * chunk is carved up in cacheline-size quanta, so that there is no chance of
+ * Current pages that are being used for internal memory allocations. These
+ * pages are carved up in cacheline-size quanta, so that there is no chance of
* false cache line sharing.
*/
-static void *base_chunk;
+static void *base_pages;
static void *base_next_addr;
-static void *base_past_addr; /* Addr immediately past base_chunk. */
+static void *base_past_addr; /* Addr immediately past base_pages. */
static chunk_node_t *base_chunk_nodes; /* LIFO cache of chunk nodes. */
static malloc_mutex_t base_mtx;
#ifdef MALLOC_STATS
@@ -851,7 +710,7 @@ static void wrtmessage(const char *p1, const char *p2, const char *p3,
static void malloc_printf(const char *format, ...);
#endif
static char *umax2s(uintmax_t x, char *s);
-static bool base_chunk_alloc(size_t minsize);
+static bool base_pages_alloc(size_t minsize);
static void *base_alloc(size_t size);
static chunk_node_t *base_chunk_node_alloc(void);
static void base_chunk_node_dealloc(chunk_node_t *node);
@@ -868,10 +727,6 @@ static arena_t *choose_arena_hard(void);
static void arena_run_split(arena_t *arena, arena_run_t *run, size_t size);
static arena_chunk_t *arena_chunk_alloc(arena_t *arena);
static void arena_chunk_dealloc(arena_t *arena, arena_chunk_t *chunk);
-static void arena_bin_run_promote(arena_t *arena, arena_bin_t *bin,
- arena_run_t *run);
-static void arena_bin_run_demote(arena_t *arena, arena_bin_t *bin,
- arena_run_t *run);
static arena_run_t *arena_run_alloc(arena_t *arena, size_t size);
static void arena_run_dalloc(arena_t *arena, arena_run_t *run, size_t size);
static arena_run_t *arena_bin_nonfull_run_get(arena_t *arena, arena_bin_t *bin);
@@ -1039,20 +894,22 @@ umax2s(uintmax_t x, char *s)
/******************************************************************************/
static bool
-base_chunk_alloc(size_t minsize)
+base_pages_alloc(size_t minsize)
{
-
- assert(minsize <= chunksize);
+ size_t csize;
#ifdef USE_BRK
/*
- * Do special brk allocation here, since the base chunk doesn't really
- * need to be chunk-aligned.
+ * Do special brk allocation here, since base allocations don't need to
+ * be chunk-aligned.
*/
if (brk_prev != (void *)-1) {
void *brk_cur;
intptr_t incr;
+ if (minsize != 0)
+ csize = CHUNK_CEILING(minsize);
+
malloc_mutex_lock(&brk_mtx);
do {
/* Get the current end of brk. */
@@ -1066,15 +923,15 @@ base_chunk_alloc(size_t minsize)
incr = (intptr_t)chunksize
- (intptr_t)CHUNK_ADDR2OFFSET(brk_cur);
if (incr < minsize)
- incr += chunksize;
+ incr += csize;
brk_prev = sbrk(incr);
if (brk_prev == brk_cur) {
/* Success. */
malloc_mutex_unlock(&brk_mtx);
- base_chunk = brk_cur;
- base_next_addr = base_chunk;
- base_past_addr = (void *)((uintptr_t)base_chunk
+ base_pages = brk_cur;
+ base_next_addr = base_pages;
+ base_past_addr = (void *)((uintptr_t)base_pages
+ incr);
#ifdef MALLOC_STATS
base_mapped += incr;
@@ -1084,19 +941,23 @@ base_chunk_alloc(size_t minsize)
} while (brk_prev != (void *)-1);
malloc_mutex_unlock(&brk_mtx);
}
+ if (minsize == 0) {
+ /*
+ * Failure during initialization doesn't matter, so avoid
+ * falling through to the mmap-based page mapping code.
+ */
+ return (true);
+ }
#endif
-
- /*
- * Don't worry about chunk alignment here, since base_chunk doesn't
- * really need to be aligned.
- */
- base_chunk = pages_map(NULL, chunksize);
- if (base_chunk == NULL)
+ assert(minsize != 0);
+ csize = PAGE_CEILING(minsize);
+ base_pages = pages_map(NULL, csize);
+ if (base_pages == NULL)
return (true);
- base_next_addr = base_chunk;
- base_past_addr = (void *)((uintptr_t)base_chunk + chunksize);
+ base_next_addr = base_pages;
+ base_past_addr = (void *)((uintptr_t)base_pages + csize);
#ifdef MALLOC_STATS
- base_mapped += chunksize;
+ base_mapped += csize;
#endif
return (false);
}
@@ -1112,13 +973,9 @@ base_alloc(size_t size)
malloc_mutex_lock(&base_mtx);
- /*
- * Make sure there's enough space for the allocation.
- * base_chunk_alloc() does not guarantee that a newly allocated chunk
- * is >= size, so loop here, rather than only trying once.
- */
- while ((uintptr_t)base_next_addr + csize > (uintptr_t)base_past_addr) {
- if (base_chunk_alloc(csize)) {
+ /* Make sure there's enough space for the allocation. */
+ if ((uintptr_t)base_next_addr + csize > (uintptr_t)base_past_addr) {
+ if (base_pages_alloc(csize)) {
ret = NULL;
goto RETURN;
}
@@ -1184,8 +1041,8 @@ stats_print(arena_t *arena)
arena->stats.nmalloc_small + arena->stats.nmalloc_large,
arena->stats.ndalloc_small + arena->stats.ndalloc_large);
- malloc_printf("bins: bin size regs pgs requests newruns "
- "maxruns curruns promote demote\n");
+ malloc_printf("bins: bin size regs pgs requests newruns"
+ " reruns maxruns curruns\n");
for (i = 0, gap_start = -1; i < ntbins + nqbins + nsbins; i++) {
if (arena->bins[i].stats.nrequests == 0) {
if (gap_start == -1)
@@ -1203,8 +1060,8 @@ stats_print(arena_t *arena)
gap_start = -1;
}
malloc_printf(
- "%13u %1s %4u %4u %3u %9llu %7llu"
- " %7lu %7lu %7llu %7llu\n",
+ "%13u %1s %4u %4u %3u %9llu %9llu"
+ " %9llu %7lu %7lu\n",
i,
i < ntbins ? "T" : i < ntbins + nqbins ? "Q" : "S",
arena->bins[i].reg_size,
@@ -1212,10 +1069,9 @@ stats_print(arena_t *arena)
arena->bins[i].run_size >> pagesize_2pow,
arena->bins[i].stats.nrequests,
arena->bins[i].stats.nruns,
+ arena->bins[i].stats.reruns,
arena->bins[i].stats.highruns,
- arena->bins[i].stats.curruns,
- arena->bins[i].stats.npromote,
- arena->bins[i].stats.ndemote);
+ arena->bins[i].stats.curruns);
}
}
if (gap_start != -1) {
@@ -1427,6 +1283,24 @@ chunk_alloc(size_t size)
/* All strategies for allocation failed. */
ret = NULL;
RETURN:
+ if (ret != NULL) {
+ chunk_node_t key;
+ /*
+ * Clean out any entries in old_chunks that overlap with the
+ * memory we just allocated.
+ */
+ key.chunk = ret;
+ tchunk = RB_NFIND(chunk_tree_s, &old_chunks, &key);
+ while (tchunk != NULL
+ && (uintptr_t)tchunk->chunk >= (uintptr_t)ret
+ && (uintptr_t)tchunk->chunk < (uintptr_t)ret + size) {
+ delchunk = tchunk;
+ tchunk = RB_NEXT(chunk_tree_s, &old_chunks, delchunk);
+ RB_REMOVE(chunk_tree_s, &old_chunks, delchunk);
+ base_chunk_node_dealloc(delchunk);
+ }
+
+ }
#ifdef MALLOC_STATS
if (ret != NULL) {
stats_chunks.nchunks += (size / chunksize);
@@ -1444,8 +1318,6 @@ RETURN:
static void
chunk_dealloc(void *chunk, size_t size)
{
- size_t offset;
- chunk_node_t key;
chunk_node_t *node;
assert(chunk != NULL);
@@ -1481,40 +1353,52 @@ chunk_dealloc(void *chunk, size_t size)
- (intptr_t)size);
brk_max = brk_prev;
}
- goto RETURN;
- } else
+ } else {
+ size_t offset;
+
malloc_mutex_unlock(&brk_mtx);
madvise(chunk, size, MADV_FREE);
- } else
+
+ /*
+ * Iteratively create records of each chunk-sized
+ * memory region that 'chunk' is comprised of, so that
+ * the address range can be recycled if memory usage
+ * increases later on.
+ */
+ for (offset = 0; offset < size; offset += chunksize) {
+ node = base_chunk_node_alloc();
+ if (node == NULL)
+ break;
+
+ node->chunk = (void *)((uintptr_t)chunk
+ + (uintptr_t)offset);
+ node->size = chunksize;
+ RB_INSERT(chunk_tree_s, &old_chunks, node);
+ }
+ }
+ } else {
#endif
pages_unmap(chunk, size);
- /*
- * Iteratively create records of each chunk-sized memory region that
- * 'chunk' is comprised of, so that the address range can be recycled
- * if memory usage increases later on.
- */
- for (offset = 0; offset < size; offset += chunksize) {
/*
- * It is possible for chunk to overlap existing entries in
- * old_chunks if it is a huge allocation, so take care to not
- * leak tree nodes.
+ * Make a record of the chunk's address, so that the address
+ * range can be recycled if memory usage increases later on.
+ * Don't bother to create entries if (size > chunksize), since
+ * doing so could cause scalability issues for truly gargantuan
+ * objects (many gigabytes or larger).
*/
- key.chunk = (void *)((uintptr_t)chunk + (uintptr_t)offset);
- if (RB_FIND(chunk_tree_s, &old_chunks, &key) == NULL) {
+ if (size == chunksize) {
node = base_chunk_node_alloc();
- if (node == NULL)
- break;
-
- node->chunk = key.chunk;
- node->size = chunksize;
- RB_INSERT(chunk_tree_s, &old_chunks, node);
+ if (node != NULL) {
+ node->chunk = (void *)(uintptr_t)chunk;
+ node->size = chunksize;
+ RB_INSERT(chunk_tree_s, &old_chunks, node);
+ }
}
- }
-
#ifdef USE_BRK
-RETURN:
+ }
#endif
+
#ifdef MALLOC_STATS
stats_chunks.curchunks -= (size / chunksize);
#endif
@@ -1656,6 +1540,24 @@ arena_chunk_comp(arena_chunk_t *a, arena_chunk_t *b)
/* Generate red-black tree code for arena chunks. */
RB_GENERATE_STATIC(arena_chunk_tree_s, arena_chunk_s, link, arena_chunk_comp);
+static inline int
+arena_run_comp(arena_run_t *a, arena_run_t *b)
+{
+
+ assert(a != NULL);
+ assert(b != NULL);
+
+ if ((uintptr_t)a < (uintptr_t)b)
+ return (-1);
+ else if (a == b)
+ return (0);
+ else
+ return (1);
+}
+
+/* Generate red-black tree code for arena runs. */
+RB_GENERATE_STATIC(arena_run_tree_s, arena_run_s, link, arena_run_comp);
+
static inline void *
arena_run_reg_alloc(arena_run_t *run, arena_bin_t *bin)
{
@@ -1663,28 +1565,51 @@ arena_run_reg_alloc(arena_run_t *run, arena_bin_t *bin)
unsigned i, mask, bit, regind;
assert(run->magic == ARENA_RUN_MAGIC);
+ assert(run->regs_minelm < bin->regs_mask_nelms);
+
+ /*
+ * Move the first check outside the loop, so that run->regs_minelm can
+ * be updated unconditionally, without the possibility of updating it
+ * multiple times.
+ */
+ i = run->regs_minelm;
+ mask = run->regs_mask[i];
+ if (mask != 0) {
+ /* Usable allocation found. */
+ bit = ffs((int)mask) - 1;
+
+ regind = ((i << (SIZEOF_INT_2POW + 3)) + bit);
+ ret = (void *)(((uintptr_t)run) + bin->reg0_offset
+ + (bin->reg_size * regind));
+
+ /* Clear bit. */
+ mask ^= (1 << bit);
+ run->regs_mask[i] = mask;
- for (i = run->regs_minelm; i < bin->regs_mask_nelms; i++) {
+ return (ret);
+ }
+
+ for (i++; i < bin->regs_mask_nelms; i++) {
mask = run->regs_mask[i];
if (mask != 0) {
/* Usable allocation found. */
bit = ffs((int)mask) - 1;
regind = ((i << (SIZEOF_INT_2POW + 3)) + bit);
- ret = (void *)&((char *)run)[bin->reg0_offset
- + (bin->reg_size * regind)];
+ ret = (void *)(((uintptr_t)run) + bin->reg0_offset
+ + (bin->reg_size * regind));
/* Clear bit. */
mask ^= (1 << bit);
run->regs_mask[i] = mask;
- return (ret);
- } else {
/*
* Make a note that nothing before this element
* contains a free region.
*/
- run->regs_minelm = i + 1;
+ run->regs_minelm = i; /* Low payoff: + (mask == 0); */
+
+ return (ret);
}
}
/* Not reached. */
@@ -1902,155 +1827,6 @@ arena_chunk_dealloc(arena_t *arena, arena_chunk_t *chunk)
}
}
-static void
-arena_bin_run_promote(arena_t *arena, arena_bin_t *bin, arena_run_t *run)
-{
-
- assert(bin == run->bin);
-
- /* Promote. */
- assert(run->free_min > run->nfree);
- assert(run->quartile < RUN_Q100);
- run->quartile++;
-#ifdef MALLOC_STATS
- bin->stats.npromote++;
-#endif
-
- /* Re-file run. */
- switch (run->quartile) {
- case RUN_QINIT:
- assert(0);
- break;
- case RUN_Q0:
- qr_before_insert(arena_bin_link(&bin->runs0), run,
- link);
- run->free_max = bin->nregs - 1;
- run->free_min = (bin->nregs >> 1) + 1;
- assert(run->nfree <= run->free_max);
- assert(run->nfree >= run->free_min);
- break;
- case RUN_Q25:
- qr_remove(run, link);
- qr_before_insert(arena_bin_link(&bin->runs25), run,
- link);
- run->free_max = ((bin->nregs >> 2) * 3) - 1;
- run->free_min = (bin->nregs >> 2) + 1;
- assert(run->nfree <= run->free_max);
- assert(run->nfree >= run->free_min);
- break;
- case RUN_Q50:
- qr_remove(run, link);
- qr_before_insert(arena_bin_link(&bin->runs50), run,
- link);
- run->free_max = (bin->nregs >> 1) - 1;
- run->free_min = 1;
- assert(run->nfree <= run->free_max);
- assert(run->nfree >= run->free_min);
- break;
- case RUN_Q75:
- /*
- * Skip RUN_Q75 during promotion from RUN_Q50.
- * Separate handling of RUN_Q75 and RUN_Q100 allows us
- * to keep completely full runs in RUN_Q100, thus
- * guaranteeing that runs in RUN_Q75 are only mostly
- * full. This provides a method for avoiding a linear
- * search for non-full runs, which avoids some
- * pathological edge cases.
- */
- run->quartile++;
-#ifdef MALLOC_STATS
- /*
- * Count as a double promotion, in order to keep
- * promotions and demotions symmetric.
- */
- bin->stats.npromote++;
-#endif
- /* Fall through. */
- case RUN_Q100:
- qr_remove(run, link);
- assert(bin->runcur == run);
- bin->runcur = NULL;
- run->free_max = 0;
- run->free_min = 0;
- assert(run->nfree <= run->free_max);
- assert(run->nfree >= run->free_min);
- break;
- default:
- assert(0);
- break;
- }
-}
-
-static void
-arena_bin_run_demote(arena_t *arena, arena_bin_t *bin, arena_run_t *run)
-{
-
- assert(bin == run->bin);
-
- /* Demote. */
- assert(run->free_max < run->nfree);
- assert(run->quartile > RUN_QINIT);
- run->quartile--;
-#ifdef MALLOC_STATS
- bin->stats.ndemote++;
-#endif
-
- /* Re-file run. */
- switch (run->quartile) {
- case RUN_QINIT:
- qr_remove(run, link);
-#ifdef MALLOC_STATS
- bin->stats.curruns--;
-#endif
- if (bin->runcur == run)
- bin->runcur = NULL;
-#ifdef MALLOC_DEBUG
- run->magic = 0;
-#endif
- arena_run_dalloc(arena, run, bin->run_size);
- break;
- case RUN_Q0:
- qr_remove(run, link);
- qr_before_insert(arena_bin_link(&bin->runs0), run,
- link);
- run->free_max = bin->nregs - 1;
- run->free_min = (bin->nregs >> 1) + 1;
- assert(run->nfree <= run->free_max);
- assert(run->nfree >= run->free_min);
- break;
- case RUN_Q25:
- qr_remove(run, link);
- qr_before_insert(arena_bin_link(&bin->runs25), run,
- link);
- run->free_max = ((bin->nregs >> 2) * 3) - 1;
- run->free_min = (bin->nregs >> 2) + 1;
- assert(run->nfree <= run->free_max);
- assert(run->nfree >= run->free_min);
- break;
- case RUN_Q50:
- qr_remove(run, link);
- qr_before_insert(arena_bin_link(&bin->runs50), run,
- link);
- run->free_max = (bin->nregs >> 1) - 1;
- run->free_min = 1;
- assert(run->nfree <= run->free_max);
- assert(run->nfree >= run->free_min);
- break;
- case RUN_Q75:
- qr_before_insert(arena_bin_link(&bin->runs75), run,
- link);
- run->free_max = (bin->nregs >> 2) - 1;
- run->free_min = 1;
- assert(run->nfree <= run->free_max);
- assert(run->nfree >= run->free_min);
- break;
- case RUN_Q100:
- default:
- assert(0);
- break;
- }
-}
-
static arena_run_t *
arena_run_alloc(arena_t *arena, size_t size)
{
@@ -2212,16 +1988,12 @@ arena_bin_nonfull_run_get(arena_t *arena, arena_bin_t *bin)
unsigned i, remainder;
/* Look for a usable run. */
- if ((run = qr_next(arena_bin_link(&bin->runs50), link))
- != arena_bin_link(&bin->runs50)
- || (run = qr_next(arena_bin_link(&bin->runs25), link))
- != arena_bin_link(&bin->runs25)
- || (run = qr_next(arena_bin_link(&bin->runs0), link))
- != arena_bin_link(&bin->runs0)
- || (run = qr_next(arena_bin_link(&bin->runs75), link))
- != arena_bin_link(&bin->runs75)) {
+ if ((run = RB_MIN(arena_run_tree_s, &bin->runs)) != NULL) {
/* run is guaranteed to have available space. */
- qr_remove(run, link);
+ RB_REMOVE(arena_run_tree_s, &bin->runs, run);
+#ifdef MALLOC_STATS
+ bin->stats.reruns++;
+#endif
return (run);
}
/* No existing runs have any space available. */
@@ -2232,25 +2004,20 @@ arena_bin_nonfull_run_get(arena_t *arena, arena_bin_t *bin)
return (NULL);
/* Initialize run internals. */
- qr_new(run, link);
run->bin = bin;
for (i = 0; i < bin->regs_mask_nelms; i++)
run->regs_mask[i] = UINT_MAX;
- remainder = bin->nregs % (1 << (SIZEOF_INT_2POW + 3));
+ remainder = bin->nregs & ((1 << (SIZEOF_INT_2POW + 3)) - 1);
if (remainder != 0) {
/* The last element has spare bits that need to be unset. */
run->regs_mask[i] = (UINT_MAX >> ((1 << (SIZEOF_INT_2POW + 3))
- remainder));
- i++;
}
run->regs_minelm = 0;
run->nfree = bin->nregs;
- run->quartile = RUN_QINIT;
- run->free_max = bin->nregs;
- run->free_min = ((bin->nregs >> 2) * 3) + 1;
#ifdef MALLOC_DEBUG
run->magic = ARENA_RUN_MAGIC;
#endif
@@ -2276,10 +2043,6 @@ arena_bin_malloc_easy(arena_t *arena, arena_bin_t *bin, arena_run_t *run)
ret = arena_run_reg_alloc(run, bin);
assert(ret != NULL);
run->nfree--;
- if (run->nfree < run->free_min) {
- /* Promote run to higher fullness quartile. */
- arena_bin_run_promote(arena, bin, run);
- }
return (ret);
}
@@ -2289,8 +2052,6 @@ static void *
arena_bin_malloc_hard(arena_t *arena, arena_bin_t *bin)
{
- assert(bin->runcur == NULL || bin->runcur->quartile == RUN_Q100);
-
bin->runcur = arena_bin_nonfull_run_get(arena, bin);
if (bin->runcur == NULL)
return (NULL);
@@ -2429,7 +2190,7 @@ arena_malloc(arena_t *arena, size_t size)
assert(size == bin->reg_size);
malloc_mutex_lock(&arena->mtx);
- if ((run = bin->runcur) != NULL)
+ if ((run = bin->runcur) != NULL && run->nfree > 0)
ret = arena_bin_malloc_easy(arena, bin, run);
else
ret = arena_bin_malloc_hard(arena, bin);
@@ -2592,7 +2353,8 @@ arena_salloc(const void *ptr)
pageind -= mapelm->pos;
- run = (arena_run_t *)&((char *)chunk)[pageind << pagesize_2pow];
+ run = (arena_run_t *)((uintptr_t)chunk + (pageind <<
+ pagesize_2pow));
assert(run->magic == ARENA_RUN_MAGIC);
ret = run->bin->reg_size;
} else
@@ -2643,9 +2405,9 @@ arena_ralloc(void *ptr, size_t size, size_t oldsize)
return (ret);
IN_PLACE:
if (opt_junk && size < oldsize)
- memset(&((char *)ptr)[size], 0x5a, oldsize - size);
+ memset((void *)((uintptr_t)ptr + size), 0x5a, oldsize - size);
else if (opt_zero && size > oldsize)
- memset(&((char *)ptr)[size], 0, size - oldsize);
+ memset((void *)((uintptr_t)ptr + size), 0, oldsize - size);
return (ptr);
}
@@ -2673,7 +2435,8 @@ arena_dalloc(arena_t *arena, arena_chunk_t *chunk, void *ptr)
pageind -= mapelm->pos;
- run = (arena_run_t *)&((char *)chunk)[pageind << pagesize_2pow];
+ run = (arena_run_t *)((uintptr_t)chunk + (pageind <<
+ pagesize_2pow));
assert(run->magic == ARENA_RUN_MAGIC);
bin = run->bin;
size = bin->reg_size;
@@ -2684,9 +2447,44 @@ arena_dalloc(arena_t *arena, arena_chunk_t *chunk, void *ptr)
malloc_mutex_lock(&arena->mtx);
arena_run_reg_dalloc(run, bin, ptr, size);
run->nfree++;
- if (run->nfree > run->free_max) {
- /* Demote run to lower fullness quartile. */
- arena_bin_run_demote(arena, bin, run);
+
+ if (run->nfree == bin->nregs) {
+ /* Deallocate run. */
+ if (run == bin->runcur)
+ bin->runcur = NULL;
+ else if (bin->nregs != 1) {
+ /*
+ * This block's conditional is necessary because
+ * if the run only contains one region, then it
+ * never gets inserted into the non-full runs
+ * tree.
+ */
+ RB_REMOVE(arena_run_tree_s, &bin->runs, run);
+ }
+#ifdef MALLOC_DEBUG
+ run->magic = 0;
+#endif
+ arena_run_dalloc(arena, run, bin->run_size);
+#ifdef MALLOC_STATS
+ bin->stats.curruns--;
+#endif
+ } else if (run->nfree == 1 && run != bin->runcur) {
+ /*
+ * Make sure that bin->runcur always refers to the
+ * lowest non-full run, if one exists.
+ */
+ if (bin->runcur == NULL)
+ bin->runcur = run;
+ else if ((uintptr_t)run < (uintptr_t)bin->runcur) {
+ /* Switch runcur. */
+ if (bin->runcur->nfree > 0) {
+ /* Insert runcur. */
+ RB_INSERT(arena_run_tree_s, &bin->runs,
+ bin->runcur);
+ }
+ bin->runcur = run;
+ } else
+ RB_INSERT(arena_run_tree_s, &bin->runs, run);
}
#ifdef MALLOC_STATS
arena->stats.allocated_small -= size;
@@ -2736,10 +2534,7 @@ arena_new(arena_t *arena)
for (i = 0; i < ntbins; i++) {
bin = &arena->bins[i];
bin->runcur = NULL;
- qr_new(arena_bin_link(&bin->runs0), link);
- qr_new(arena_bin_link(&bin->runs25), link);
- qr_new(arena_bin_link(&bin->runs50), link);
- qr_new(arena_bin_link(&bin->runs75), link);
+ RB_INIT(&bin->runs);
bin->reg_size = (1 << (TINY_MIN_2POW + i));
@@ -2754,10 +2549,7 @@ arena_new(arena_t *arena)
for (; i < ntbins + nqbins; i++) {
bin = &arena->bins[i];
bin->runcur = NULL;
- qr_new(arena_bin_link(&bin->runs0), link);
- qr_new(arena_bin_link(&bin->runs25), link);
- qr_new(arena_bin_link(&bin->runs50), link);
- qr_new(arena_bin_link(&bin->runs75), link);
+ RB_INIT(&bin->runs);
bin->reg_size = quantum * (i - ntbins + 1);
@@ -2773,10 +2565,7 @@ arena_new(arena_t *arena)
for (; i < ntbins + nqbins + nsbins; i++) {
bin = &arena->bins[i];
bin->runcur = NULL;
- qr_new(arena_bin_link(&bin->runs0), link);
- qr_new(arena_bin_link(&bin->runs25), link);
- qr_new(arena_bin_link(&bin->runs50), link);
- qr_new(arena_bin_link(&bin->runs75), link);
+ RB_INIT(&bin->runs);
bin->reg_size = (small_max << (i - (ntbins + nqbins) + 1));
@@ -2944,7 +2733,8 @@ huge_palloc(size_t alignment, size_t size)
malloc_mutex_lock(&chunks_mtx);
RB_INSERT(chunk_tree_s, &huge, node);
#ifdef MALLOC_STATS
- huge_allocated += size;
+ huge_nmalloc++;
+ huge_allocated += chunk_size;
#endif
malloc_mutex_unlock(&chunks_mtx);
@@ -3006,7 +2796,6 @@ huge_dalloc(void *ptr)
RB_REMOVE(chunk_tree_s, &huge, node);
#ifdef MALLOC_STATS
- /* Update counters. */
huge_ndalloc++;
huge_allocated -= node->size;
#endif
@@ -3482,11 +3271,11 @@ malloc_init_hard(void)
break;
case 'k':
/*
- * Run fullness quartile limits don't have
- * enough resolution if there are too few
- * regions for the largest bin size classes.
+ * Chunks always require at least one header
+ * page, so chunks can never be smaller than
+ * two pages.
*/
- if (opt_chunk_2pow > pagesize_2pow + 4)
+ if (opt_chunk_2pow > pagesize_2pow + 1)
opt_chunk_2pow--;
break;
case 'K':
@@ -3647,7 +3436,7 @@ malloc_init_hard(void)
* chunk-aligned. Doing this before allocating any other chunks allows
* the use of space that would otherwise be wasted.
*/
- base_chunk_alloc(0);
+ base_pages_alloc(0);
#endif
base_chunk_nodes = NULL;
malloc_mutex_init(&base_mtx);
OpenPOWER on IntegriCloud