summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.in1
-rw-r--r--include/jemalloc/internal/arena.h20
-rw-r--r--include/jemalloc/internal/jemalloc_internal.h.in5
-rw-r--r--include/jemalloc/internal/ph.h556
-rw-r--r--include/jemalloc/internal/private_symbols.txt16
-rw-r--r--src/arena.c95
-rw-r--r--src/ph.c2
-rw-r--r--test/unit/ph.c257
8 files changed, 615 insertions, 337 deletions
diff --git a/Makefile.in b/Makefile.in
index 7f2d668..480ce1a 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -95,7 +95,6 @@ C_SRCS := $(srcroot)src/jemalloc.c \
$(srcroot)src/mutex.c \
$(srcroot)src/nstime.c \
$(srcroot)src/pages.c \
- $(srcroot)src/ph.c \
$(srcroot)src/prng.c \
$(srcroot)src/prof.c \
$(srcroot)src/quarantine.c \
diff --git a/include/jemalloc/internal/arena.h b/include/jemalloc/internal/arena.h
index 09ae689..6f0fa76 100644
--- a/include/jemalloc/internal/arena.h
+++ b/include/jemalloc/internal/arena.h
@@ -160,7 +160,7 @@ struct arena_chunk_map_misc_s {
* 2) arena_run_t conceptually uses this linkage for in-use non-full
* runs, rather than directly embedding linkage.
*/
- ph_node_t ph_link;
+ phn(arena_chunk_map_misc_t) ph_link;
union {
/* Linkage for list of dirty runs. */
@@ -176,6 +176,7 @@ struct arena_chunk_map_misc_s {
arena_run_t run;
};
};
+typedef ph(arena_chunk_map_misc_t) arena_run_heap_t;
#endif /* JEMALLOC_ARENA_STRUCTS_A */
#ifdef JEMALLOC_ARENA_STRUCTS_B
@@ -278,7 +279,7 @@ struct arena_bin_s {
* objects packed well, and it can also help reduce the number of
* almost-empty chunks.
*/
- ph_heap_t runs;
+ arena_run_heap_t runs;
/* Bin statistics. */
malloc_bin_stats_t stats;
@@ -460,7 +461,7 @@ struct arena_s {
* Quantized address-ordered heaps of this arena's available runs. The
* heaps are used for first-best-fit run allocation.
*/
- ph_heap_t runs_avail[1]; /* Dynamically sized. */
+ arena_run_heap_t runs_avail[1]; /* Dynamically sized. */
};
/* Used in conjunction with tsd for fast arena-related context lookup. */
@@ -604,7 +605,6 @@ const arena_chunk_map_misc_t *arena_miscelm_get_const(
size_t arena_miscelm_to_pageind(const arena_chunk_map_misc_t *miscelm);
void *arena_miscelm_to_rpages(const arena_chunk_map_misc_t *miscelm);
arena_chunk_map_misc_t *arena_rd_to_miscelm(arena_runs_dirty_link_t *rd);
-arena_chunk_map_misc_t *arena_ph_to_miscelm(ph_node_t *ph);
arena_chunk_map_misc_t *arena_run_to_miscelm(arena_run_t *run);
size_t *arena_mapbitsp_get_mutable(arena_chunk_t *chunk, size_t pageind);
const size_t *arena_mapbitsp_get_const(const arena_chunk_t *chunk,
@@ -735,18 +735,6 @@ arena_rd_to_miscelm(arena_runs_dirty_link_t *rd)
}
JEMALLOC_ALWAYS_INLINE arena_chunk_map_misc_t *
-arena_ph_to_miscelm(ph_node_t *ph)
-{
- arena_chunk_map_misc_t *miscelm = (arena_chunk_map_misc_t *)
- ((uintptr_t)ph - offsetof(arena_chunk_map_misc_t, ph_link));
-
- assert(arena_miscelm_to_pageind(miscelm) >= map_bias);
- assert(arena_miscelm_to_pageind(miscelm) < chunk_npages);
-
- return (miscelm);
-}
-
-JEMALLOC_ALWAYS_INLINE arena_chunk_map_misc_t *
arena_run_to_miscelm(arena_run_t *run)
{
arena_chunk_map_misc_t *miscelm = (arena_chunk_map_misc_t
diff --git a/include/jemalloc/internal/jemalloc_internal.h.in b/include/jemalloc/internal/jemalloc_internal.h.in
index c1cccd6..55ca714 100644
--- a/include/jemalloc/internal/jemalloc_internal.h.in
+++ b/include/jemalloc/internal/jemalloc_internal.h.in
@@ -161,6 +161,7 @@ static const bool config_cache_oblivious =
#include <malloc/malloc.h>
#endif
+#include "jemalloc/internal/ph.h"
#define RB_COMPACT
#include "jemalloc/internal/rb.h"
#include "jemalloc/internal/qr.h"
@@ -371,7 +372,6 @@ typedef unsigned szind_t;
#include "jemalloc/internal/tsd.h"
#include "jemalloc/internal/mb.h"
#include "jemalloc/internal/extent.h"
-#include "jemalloc/internal/ph.h"
#include "jemalloc/internal/arena.h"
#include "jemalloc/internal/bitmap.h"
#include "jemalloc/internal/base.h"
@@ -402,7 +402,6 @@ typedef unsigned szind_t;
#include "jemalloc/internal/mutex.h"
#include "jemalloc/internal/mb.h"
#include "jemalloc/internal/bitmap.h"
-#include "jemalloc/internal/ph.h"
#define JEMALLOC_ARENA_STRUCTS_A
#include "jemalloc/internal/arena.h"
#undef JEMALLOC_ARENA_STRUCTS_A
@@ -495,7 +494,6 @@ void jemalloc_postfork_child(void);
#include "jemalloc/internal/mb.h"
#include "jemalloc/internal/bitmap.h"
#include "jemalloc/internal/extent.h"
-#include "jemalloc/internal/ph.h"
#include "jemalloc/internal/arena.h"
#include "jemalloc/internal/base.h"
#include "jemalloc/internal/rtree.h"
@@ -527,7 +525,6 @@ void jemalloc_postfork_child(void);
#include "jemalloc/internal/tsd.h"
#include "jemalloc/internal/mb.h"
#include "jemalloc/internal/extent.h"
-#include "jemalloc/internal/ph.h"
#include "jemalloc/internal/base.h"
#include "jemalloc/internal/rtree.h"
#include "jemalloc/internal/pages.h"
diff --git a/include/jemalloc/internal/ph.h b/include/jemalloc/internal/ph.h
index 519f0dd..70b6e2c 100644
--- a/include/jemalloc/internal/ph.h
+++ b/include/jemalloc/internal/ph.h
@@ -4,257 +4,341 @@
* "The Pairing Heap: A New Form of Self-Adjusting Heap"
* https://www.cs.cmu.edu/~sleator/papers/pairing-heaps.pdf
*
- * With auxiliary list, described in a follow on paper
+ * With auxiliary twopass list, described in a follow on paper.
*
* "Pairing Heaps: Experiments and Analysis"
* http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.106.2988&rep=rep1&type=pdf
*
- * Where search/nsearch/last are not needed, ph.h outperforms rb.h by ~7x fewer
- * cpu cycles, and ~4x fewer memory references.
- *
- * Tagging parent/prev pointers on the next list was also described in the
- * original paper, such that only two pointers are needed. This is not
- * implemented here, as it substantially increases the memory references
- * needed when ph_remove is called, almost overshadowing the other performance
- * gains.
- *
*******************************************************************************
*/
-#ifdef JEMALLOC_H_TYPES
-
-typedef struct ph_node_s ph_node_t;
-typedef struct ph_heap_s ph_heap_t;
-
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
-
-struct ph_node_s {
- ph_node_t *subheaps;
- ph_node_t *parent;
- ph_node_t *next;
- ph_node_t *prev;
-};
-
-struct ph_heap_s {
- ph_node_t *root;
-};
-
-#endif /* JEMALLOC_H_STRUCTS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
-
-#endif /* JEMALLOC_H_EXTERNS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
-
-#ifndef JEMALLOC_ENABLE_INLINE
-ph_node_t *ph_merge_ordered(ph_node_t *heap1, ph_node_t *heap2);
-ph_node_t *ph_merge(ph_node_t *heap1, ph_node_t *heap2);
-ph_node_t *ph_merge_pairs(ph_node_t *subheaps);
-void ph_merge_aux_list(ph_heap_t *l);
-void ph_new(ph_heap_t *n);
-ph_node_t *ph_first(ph_heap_t *l);
-void ph_insert(ph_heap_t *l, ph_node_t *n);
-ph_node_t *ph_remove_first(ph_heap_t *l);
-void ph_remove(ph_heap_t *l, ph_node_t *n);
-#endif
-
-#if (defined(JEMALLOC_ENABLE_INLINE) || defined(JEMALLOC_PH_C_))
-/* Helper routines ************************************************************/
+#ifndef PH_H_
+#define PH_H_
-JEMALLOC_INLINE ph_node_t *
-ph_merge_ordered(ph_node_t *heap1, ph_node_t *heap2)
-{
-
- assert(heap1 != NULL);
- assert(heap2 != NULL);
- assert ((uintptr_t)heap1 <= (uintptr_t)heap2);
-
- heap2->parent = heap1;
- heap2->prev = NULL;
- heap2->next = heap1->subheaps;
- if (heap1->subheaps != NULL)
- heap1->subheaps->prev = heap2;
- heap1->subheaps = heap2;
- return (heap1);
+/* Node structure. */
+#define phn(a_type) \
+struct { \
+ a_type *phn_prev; \
+ a_type *phn_next; \
+ a_type *phn_lchild; \
}
-JEMALLOC_INLINE ph_node_t *
-ph_merge(ph_node_t *heap1, ph_node_t *heap2)
-{
-
- if (heap1 == NULL)
- return (heap2);
- if (heap2 == NULL)
- return (heap1);
- /* Optional: user-settable comparison function */
- if ((uintptr_t)heap1 < (uintptr_t)heap2)
- return (ph_merge_ordered(heap1, heap2));
- else
- return (ph_merge_ordered(heap2, heap1));
+/* Root structure. */
+#define ph(a_type) \
+struct { \
+ a_type *ph_root; \
}
-JEMALLOC_INLINE ph_node_t *
-ph_merge_pairs(ph_node_t *subheaps)
-{
-
- if (subheaps == NULL)
- return (NULL);
- if (subheaps->next == NULL)
- return (subheaps);
- {
- ph_node_t *l0 = subheaps;
- ph_node_t *l1 = l0->next;
- ph_node_t *lrest = l1->next;
-
- if (lrest != NULL)
- lrest->prev = NULL;
- l1->next = NULL;
- l1->prev = NULL;
- l0->next = NULL;
- l0->prev = NULL;
- return (ph_merge(ph_merge(l0, l1), ph_merge_pairs(lrest)));
- }
-}
+/* Internal utility macros. */
+#define phn_lchild_get(a_type, a_field, a_phn) \
+ (a_phn->a_field.phn_lchild)
+#define phn_lchild_set(a_type, a_field, a_phn, a_lchild) do { \
+ a_phn->a_field.phn_lchild = a_lchild; \
+} while (0)
+
+#define phn_next_get(a_type, a_field, a_phn) \
+ (a_phn->a_field.phn_next)
+#define phn_prev_set(a_type, a_field, a_phn, a_prev) do { \
+ a_phn->a_field.phn_prev = a_prev; \
+} while (0)
+
+#define phn_prev_get(a_type, a_field, a_phn) \
+ (a_phn->a_field.phn_prev)
+#define phn_next_set(a_type, a_field, a_phn, a_next) do { \
+ a_phn->a_field.phn_next = a_next; \
+} while (0)
+
+#define phn_merge_ordered(a_type, a_field, a_phn0, a_phn1, a_cmp) do { \
+ a_type *phn0child; \
+ \
+ assert(a_phn0 != NULL); \
+ assert(a_phn1 != NULL); \
+ assert(a_cmp(a_phn0, a_phn1) <= 0); \
+ \
+ phn_prev_set(a_type, a_field, a_phn1, a_phn0); \
+ phn0child = phn_lchild_get(a_type, a_field, a_phn0); \
+ phn_next_set(a_type, a_field, a_phn1, phn0child); \
+ if (phn0child != NULL) \
+ phn_prev_set(a_type, a_field, phn0child, a_phn1); \
+ phn_lchild_set(a_type, a_field, a_phn0, a_phn1); \
+} while (0)
+
+#define phn_merge(a_type, a_field, a_phn0, a_phn1, a_cmp, r_phn) do { \
+ if (a_phn0 == NULL) \
+ r_phn = a_phn1; \
+ else if (a_phn1 == NULL) \
+ r_phn = a_phn0; \
+ else if (a_cmp(a_phn0, a_phn1) < 0) { \
+ phn_merge_ordered(a_type, a_field, a_phn0, a_phn1, \
+ a_cmp); \
+ r_phn = a_phn0; \
+ } else { \
+ phn_merge_ordered(a_type, a_field, a_phn1, a_phn0, \
+ a_cmp); \
+ r_phn = a_phn1; \
+ } \
+} while (0)
+
+#define ph_merge_siblings(a_type, a_field, a_phn, a_cmp, r_phn) do { \
+ a_type *head = NULL; \
+ a_type *tail = NULL; \
+ a_type *phn0 = a_phn; \
+ a_type *phn1 = phn_next_get(a_type, a_field, phn0); \
+ \
+ /* \
+ * Multipass merge, wherein the first two elements of a FIFO \
+ * are repeatedly merged, and each result is appended to the \
+ * singly linked FIFO, until the FIFO contains only a single \
+ * element. We start with a sibling list but no reference to \
+ * its tail, so we do a single pass over the sibling list to \
+ * populate the FIFO. \
+ */ \
+ if (phn1 != NULL) { \
+ a_type *phnrest = phn_next_get(a_type, a_field, phn1); \
+ if (phnrest != NULL) \
+ phn_prev_set(a_type, a_field, phnrest, NULL); \
+ phn_prev_set(a_type, a_field, phn0, NULL); \
+ phn_next_set(a_type, a_field, phn0, NULL); \
+ phn_prev_set(a_type, a_field, phn1, NULL); \
+ phn_next_set(a_type, a_field, phn1, NULL); \
+ phn_merge(a_type, a_field, phn0, phn1, a_cmp, phn0); \
+ head = tail = phn0; \
+ phn0 = phnrest; \
+ while (phn0 != NULL) { \
+ phn1 = phn_next_get(a_type, a_field, phn0); \
+ if (phn1 != NULL) { \
+ phnrest = phn_next_get(a_type, a_field, \
+ phn1); \
+ if (phnrest != NULL) { \
+ phn_prev_set(a_type, a_field, \
+ phnrest, NULL); \
+ } \
+ phn_prev_set(a_type, a_field, phn0, \
+ NULL); \
+ phn_next_set(a_type, a_field, phn0, \
+ NULL); \
+ phn_prev_set(a_type, a_field, phn1, \
+ NULL); \
+ phn_next_set(a_type, a_field, phn1, \
+ NULL); \
+ phn_merge(a_type, a_field, phn0, phn1, \
+ a_cmp, phn0); \
+ phn_next_set(a_type, a_field, tail, \
+ phn0); \
+ tail = phn0; \
+ phn0 = phnrest; \
+ } else { \
+ phn_next_set(a_type, a_field, tail, \
+ phn0); \
+ tail = phn0; \
+ phn0 = NULL; \
+ } \
+ } \
+ phn0 = head; \
+ phn1 = phn_next_get(a_type, a_field, phn0); \
+ if (phn1 != NULL) { \
+ while (true) { \
+ head = phn_next_get(a_type, a_field, \
+ phn1); \
+ assert(phn_prev_get(a_type, a_field, \
+ phn0) == NULL); \
+ phn_next_set(a_type, a_field, phn0, \
+ NULL); \
+ assert(phn_prev_get(a_type, a_field, \
+ phn1) == NULL); \
+ phn_next_set(a_type, a_field, phn1, \
+ NULL); \
+ phn_merge(a_type, a_field, phn0, phn1, \
+ a_cmp, phn0); \
+ if (head == NULL) \
+ break; \
+ phn_next_set(a_type, a_field, tail, \
+ phn0); \
+ tail = phn0; \
+ phn0 = head; \
+ phn1 = phn_next_get(a_type, a_field, \
+ phn0); \
+ } \
+ } \
+ } \
+ r_phn = phn0; \
+} while (0)
+
+#define ph_merge_aux(a_type, a_field, a_ph, a_cmp) do { \
+ a_type *phn = phn_next_get(a_type, a_field, a_ph->ph_root); \
+ if (phn != NULL) { \
+ phn_prev_set(a_type, a_field, a_ph->ph_root, NULL); \
+ phn_next_set(a_type, a_field, a_ph->ph_root, NULL); \
+ phn_prev_set(a_type, a_field, phn, NULL); \
+ ph_merge_siblings(a_type, a_field, phn, a_cmp, phn); \
+ assert(phn_next_get(a_type, a_field, phn) == NULL); \
+ phn_merge(a_type, a_field, a_ph->ph_root, phn, a_cmp, \
+ a_ph->ph_root); \
+ } \
+} while (0)
+
+#define ph_merge_children(a_type, a_field, a_phn, a_cmp, r_phn) do { \
+ a_type *lchild = phn_lchild_get(a_type, a_field, a_phn); \
+ if (lchild == NULL) \
+ r_phn = NULL; \
+ else { \
+ ph_merge_siblings(a_type, a_field, lchild, a_cmp, \
+ r_phn); \
+ } \
+} while (0)
/*
- * Merge the aux list into the root node.
+ * The ph_proto() macro generates function prototypes that correspond to the
+ * functions generated by an equivalently parameterized call to ph_gen().
*/
-JEMALLOC_INLINE void
-ph_merge_aux_list(ph_heap_t *l)
-{
-
- if (l->root == NULL)
- return;
- if (l->root->next != NULL) {
- ph_node_t *l0 = l->root->next;
- ph_node_t *l1 = l0->next;
- ph_node_t *lrest = NULL;
-
- /* Multipass merge. */
- while (l1 != NULL) {
- lrest = l1->next;
- if (lrest != NULL)
- lrest->prev = NULL;
- l1->next = NULL;
- l1->prev = NULL;
- l0->next = NULL;
- l0->prev = NULL;
- l0 = ph_merge(l0, l1);
- l1 = lrest;
- }
- l->root->next = NULL;
- l->root = ph_merge(l->root, l0);
- }
-}
-
-/* User API *******************************************************************/
+#define ph_proto(a_attr, a_prefix, a_ph_type, a_type) \
+a_attr void a_prefix##new(a_ph_type *ph); \
+a_attr bool a_prefix##empty(a_ph_type *ph); \
+a_attr a_type *a_prefix##first(a_ph_type *ph); \
+a_attr void a_prefix##insert(a_ph_type *ph, a_type *phn); \
+a_attr a_type *a_prefix##remove_first(a_ph_type *ph); \
+a_attr void a_prefix##remove(a_ph_type *ph, a_type *phn);
-JEMALLOC_INLINE void
-ph_new(ph_heap_t *n)
-{
-
- memset(n, 0, sizeof(ph_heap_t));
-}
-
-JEMALLOC_INLINE ph_node_t *
-ph_first(ph_heap_t *l)
-{
-
- /*
- * For the cost of an extra pointer, a l->min could be stored instead of
- * merging the aux list here. Current users always call ph_remove(l,
- * ph_first(l)) though, and the aux list must always be merged for
- * delete of the min node anyway.
- */
- ph_merge_aux_list(l);
- return (l->root);
-}
-
-JEMALLOC_INLINE void
-ph_insert(ph_heap_t *l, ph_node_t *n)
-{
-
- memset(n, 0, sizeof(ph_node_t));
-
- /*
- * Non-aux list insert:
- *
- * l->root = ph_merge(l->root, n);
- *
- * Aux list insert:
- */
- if (l->root == NULL)
- l->root = n;
- else {
- n->next = l->root->next;
- if (l->root->next != NULL)
- l->root->next->prev = n;
- n->prev = l->root;
- l->root->next = n;
- }
-}
-
-JEMALLOC_INLINE ph_node_t *
-ph_remove_first(ph_heap_t *l)
-{
- ph_node_t *ret;
-
- ph_merge_aux_list(l);
- if (l->root == NULL)
- return (NULL);
-
- ret = l->root;
-
- l->root = ph_merge_pairs(l->root->subheaps);
-
- return (ret);
-}
-
-JEMALLOC_INLINE void
-ph_remove(ph_heap_t *l, ph_node_t *n)
-{
- ph_node_t *replace;
-
- /*
- * We can delete from aux list without merging it, but we need to merge
- * if we are dealing with the root node.
- */
- if (l->root == n) {
- ph_merge_aux_list(l);
- if (l->root == n) {
- ph_remove_first(l);
- return;
- }
- }
-
- /* Find a possible replacement node, and link to parent. */
- replace = ph_merge_pairs(n->subheaps);
- if (n->parent != NULL && n->parent->subheaps == n) {
- if (replace != NULL)
- n->parent->subheaps = replace;
- else
- n->parent->subheaps = n->next;
- }
- /* Set next/prev for sibling linked list. */
- if (replace != NULL) {
- replace->parent = n->parent;
- replace->prev = n->prev;
- if (n->prev != NULL)
- n->prev->next = replace;
- replace->next = n->next;
- if (n->next != NULL)
- n->next->prev = replace;
- } else {
- if (n->prev != NULL)
- n->prev->next = n->next;
- if (n->next != NULL)
- n->next->prev = n->prev;
- }
+/*
+ * The ph_gen() macro generates a type-specific pairing heap implementation,
+ * based on the above cpp macros.
+ */
+#define ph_gen(a_attr, a_prefix, a_ph_type, a_type, a_field, a_cmp) \
+a_attr void \
+a_prefix##new(a_ph_type *ph) \
+{ \
+ \
+ memset(ph, 0, sizeof(ph(a_type))); \
+} \
+a_attr bool \
+a_prefix##empty(a_ph_type *ph) { \
+ \
+ return (ph->ph_root == NULL); \
+} \
+a_attr a_type * \
+a_prefix##first(a_ph_type *ph) \
+{ \
+ \
+ if (ph->ph_root == NULL) \
+ return (NULL); \
+ ph_merge_aux(a_type, a_field, ph, a_cmp); \
+ return (ph->ph_root); \
+} \
+a_attr void \
+a_prefix##insert(a_ph_type *ph, a_type *phn) \
+{ \
+ \
+ memset(&phn->a_field, 0, sizeof(phn(a_type))); \
+ \
+ /* \
+ * Treat the root as an aux list during insertion, and lazily \
+ * merge during a_prefix##remove_first(). For elements that \
+ * are inserted, then removed via a_prefix##remove() before the \
+ * aux list is ever processed, this makes insert/remove \
+ * constant-time, whereas eager merging would make insert \
+ * O(log n). \
+ */ \
+ if (ph->ph_root == NULL) \
+ ph->ph_root = phn; \
+ else { \
+ phn_next_set(a_type, a_field, phn, phn_next_get(a_type, \
+ a_field, ph->ph_root)); \
+ if (phn_next_get(a_type, a_field, ph->ph_root) != \
+ NULL) { \
+ phn_prev_set(a_type, a_field, \
+ phn_next_get(a_type, a_field, ph->ph_root), \
+ phn); \
+ } \
+ phn_prev_set(a_type, a_field, phn, ph->ph_root); \
+ phn_next_set(a_type, a_field, ph->ph_root, phn); \
+ } \
+} \
+a_attr a_type * \
+a_prefix##remove_first(a_ph_type *ph) \
+{ \
+ a_type *ret; \
+ \
+ if (ph->ph_root == NULL) \
+ return (NULL); \
+ ph_merge_aux(a_type, a_field, ph, a_cmp); \
+ \
+ ret = ph->ph_root; \
+ \
+ ph_merge_children(a_type, a_field, ph->ph_root, a_cmp, \
+ ph->ph_root); \
+ \
+ return (ret); \
+} \
+a_attr void \
+a_prefix##remove(a_ph_type *ph, a_type *phn) \
+{ \
+ a_type *replace, *parent; \
+ \
+ /* \
+ * We can delete from aux list without merging it, but we need \
+ * to merge if we are dealing with the root node. \
+ */ \
+ if (ph->ph_root == phn) { \
+ ph_merge_aux(a_type, a_field, ph, a_cmp); \
+ if (ph->ph_root == phn) { \
+ ph_merge_children(a_type, a_field, ph->ph_root, \
+ a_cmp, ph->ph_root); \
+ return; \
+ } \
+ } \
+ \
+ /* Get parent (if phn is leftmost child) before mutating. */ \
+ if ((parent = phn_prev_get(a_type, a_field, phn)) != NULL) { \
+ if (phn_lchild_get(a_type, a_field, parent) != phn) \
+ parent = NULL; \
+ } \
+ /* Find a possible replacement node, and link to parent. */ \
+ ph_merge_children(a_type, a_field, phn, a_cmp, replace); \
+ /* Set next/prev for sibling linked list. */ \
+ if (replace != NULL) { \
+ if (parent != NULL) { \
+ phn_prev_set(a_type, a_field, replace, parent); \
+ phn_lchild_set(a_type, a_field, parent, \
+ replace); \
+ } else { \
+ phn_prev_set(a_type, a_field, replace, \
+ phn_prev_get(a_type, a_field, phn)); \
+ if (phn_prev_get(a_type, a_field, phn) != \
+ NULL) { \
+ phn_next_set(a_type, a_field, \
+ phn_prev_get(a_type, a_field, phn), \
+ replace); \
+ } \
+ } \
+ phn_next_set(a_type, a_field, replace, \
+ phn_next_get(a_type, a_field, phn)); \
+ if (phn_next_get(a_type, a_field, phn) != NULL) { \
+ phn_prev_set(a_type, a_field, \
+ phn_next_get(a_type, a_field, phn), \
+ replace); \
+ } \
+ } else { \
+ if (parent != NULL) { \
+ a_type *next = phn_next_get(a_type, a_field, \
+ phn); \
+ phn_lchild_set(a_type, a_field, parent, next); \
+ if (next != NULL) { \
+ phn_prev_set(a_type, a_field, next, \
+ parent); \
+ } \
+ } else { \
+ assert(phn_prev_get(a_type, a_field, phn) != \
+ NULL); \
+ phn_next_set(a_type, a_field, \
+ phn_prev_get(a_type, a_field, phn), \
+ phn_next_get(a_type, a_field, phn)); \
+ } \
+ if (phn_next_get(a_type, a_field, phn) != NULL) { \
+ phn_prev_set(a_type, a_field, \
+ phn_next_get(a_type, a_field, phn), \
+ phn_prev_get(a_type, a_field, phn)); \
+ } \
+ } \
}
-#endif
-#endif /* JEMALLOC_H_INLINES */
-/******************************************************************************/
+#endif /* PH_H_ */
diff --git a/include/jemalloc/internal/private_symbols.txt b/include/jemalloc/internal/private_symbols.txt
index 969c73d..551cb93 100644
--- a/include/jemalloc/internal/private_symbols.txt
+++ b/include/jemalloc/internal/private_symbols.txt
@@ -82,7 +82,6 @@ arena_nthreads_dec
arena_nthreads_get
arena_nthreads_inc
arena_palloc
-arena_ph_to_miscelm
arena_postfork_child
arena_postfork_parent
arena_prefork
@@ -101,6 +100,12 @@ arena_ralloc_junk_large
arena_ralloc_no_move
arena_rd_to_miscelm
arena_redzone_corruption
+arena_run_heap_empty
+arena_run_heap_first
+arena_run_heap_insert
+arena_run_heap_new
+arena_run_heap_remove_first
+arena_run_heap_remove
arena_run_regind
arena_run_to_miscelm
arena_salloc
@@ -381,15 +386,6 @@ pages_map
pages_purge
pages_trim
pages_unmap
-ph_first
-ph_insert
-ph_merge
-ph_merge_aux_list
-ph_merge_ordered
-ph_merge_pairs
-ph_new
-ph_remove_first
-ph_remove
pow2_ceil_u32
pow2_ceil_u64
pow2_ceil_zu
diff --git a/src/arena.c b/src/arena.c
index 1d30de5..d884dc4 100644
--- a/src/arena.c
+++ b/src/arena.c
@@ -59,6 +59,23 @@ arena_miscelm_size_get(const arena_chunk_map_misc_t *miscelm)
return (arena_mapbits_size_decode(mapbits));
}
+JEMALLOC_INLINE_C int
+arena_run_addr_comp(const arena_chunk_map_misc_t *a,
+ const arena_chunk_map_misc_t *b)
+{
+ uintptr_t a_miscelm = (uintptr_t)a;
+ uintptr_t b_miscelm = (uintptr_t)b;
+
+ assert(a != NULL);
+ assert(b != NULL);
+
+ return ((a_miscelm > b_miscelm) - (a_miscelm < b_miscelm));
+}
+
+/* Generate pairing heap functions. */
+ph_gen(static UNUSED, arena_run_heap_, arena_run_heap_t, arena_chunk_map_misc_t,
+ ph_link, arena_run_addr_comp)
+
static size_t
run_quantize_floor_compute(size_t size)
{
@@ -182,7 +199,7 @@ run_quantize_ceil(size_t size)
run_quantize_t *run_quantize_ceil = JEMALLOC_N(run_quantize_ceil_impl);
#endif
-static ph_heap_t *
+static arena_run_heap_t *
arena_runs_avail_get(arena_t *arena, szind_t ind)
{
@@ -200,8 +217,8 @@ arena_avail_insert(arena_t *arena, arena_chunk_t *chunk, size_t pageind,
arena_miscelm_get_const(chunk, pageind))));
assert(npages == (arena_mapbits_unallocated_size_get(chunk, pageind) >>
LG_PAGE));
- ph_insert(arena_runs_avail_get(arena, ind),
- &arena_miscelm_get_mutable(chunk, pageind)->ph_link);
+ arena_run_heap_insert(arena_runs_avail_get(arena, ind),
+ arena_miscelm_get_mutable(chunk, pageind));
}
static void
@@ -212,8 +229,8 @@ arena_avail_remove(arena_t *arena, arena_chunk_t *chunk, size_t pageind,
arena_miscelm_get_const(chunk, pageind))));
assert(npages == (arena_mapbits_unallocated_size_get(chunk, pageind) >>
LG_PAGE));
- ph_remove(arena_runs_avail_get(arena, ind),
- &arena_miscelm_get_mutable(chunk, pageind)->ph_link);
+ arena_run_heap_remove(arena_runs_avail_get(arena, ind),
+ arena_miscelm_get_mutable(chunk, pageind));
}
static void
@@ -1065,12 +1082,10 @@ arena_run_first_best_fit(arena_t *arena, size_t size)
ind = size2index(run_quantize_ceil(size));
for (i = ind; i < runs_avail_nclasses + runs_avail_bias; i++) {
- ph_node_t *node = ph_first(arena_runs_avail_get(arena, i));
- if (node != NULL) {
- arena_chunk_map_misc_t *miscelm =
- arena_ph_to_miscelm(node);
+ arena_chunk_map_misc_t *miscelm = arena_run_heap_first(
+ arena_runs_avail_get(arena, i));
+ if (miscelm != NULL)
return (&miscelm->run);
- }
}
return (NULL);
@@ -2052,45 +2067,26 @@ arena_run_trim_tail(arena_t *arena, arena_chunk_t *chunk, arena_run_t *run,
0));
}
-static arena_run_t *
-arena_bin_runs_first(arena_bin_t *bin)
-{
- ph_node_t *node;
- arena_chunk_map_misc_t *miscelm;
-
- node = ph_first(&bin->runs);
- if (node == NULL)
- return (NULL);
- miscelm = arena_ph_to_miscelm(node);
- return (&miscelm->run);
-}
-
static void
arena_bin_runs_insert(arena_bin_t *bin, arena_run_t *run)
{
arena_chunk_map_misc_t *miscelm = arena_run_to_miscelm(run);
- ph_insert(&bin->runs, &miscelm->ph_link);
-}
-
-static void
-arena_bin_runs_remove(arena_bin_t *bin, arena_run_t *run)
-{
- arena_chunk_map_misc_t *miscelm = arena_run_to_miscelm(run);
-
- ph_remove(&bin->runs, &miscelm->ph_link);
+ arena_run_heap_insert(&bin->runs, miscelm);
}
static arena_run_t *
arena_bin_nonfull_run_tryget(arena_bin_t *bin)
{
- arena_run_t *run = arena_bin_runs_first(bin);
- if (run != NULL) {
- arena_bin_runs_remove(bin, run);
- if (config_stats)
- bin->stats.reruns++;
- }
- return (run);
+ arena_chunk_map_misc_t *miscelm;
+
+ miscelm = arena_run_heap_remove_first(&bin->runs);
+ if (miscelm == NULL)
+ return (NULL);
+ if (config_stats)
+ bin->stats.reruns++;
+
+ return (&miscelm->run);
}
static arena_run_t *
@@ -2645,13 +2641,16 @@ arena_dissociate_bin_run(arena_chunk_t *chunk, arena_run_t *run,
&chunk->node), bin);
arena_bin_info_t *bin_info = &arena_bin_info[binind];
+ /*
+ * The following block's conditional is necessary because if the
+ * run only contains one region, then it never gets inserted
+ * into the non-full runs tree.
+ */
if (bin_info->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.
- */
- arena_bin_runs_remove(bin, run);
+ arena_chunk_map_misc_t *miscelm =
+ arena_run_to_miscelm(run);
+
+ arena_run_heap_remove(&bin->runs, miscelm);
}
}
}
@@ -3312,7 +3311,7 @@ arena_new(unsigned ind)
arena_bin_t *bin;
/* Compute arena size to incorporate sufficient runs_avail elements. */
- arena_size = offsetof(arena_t, runs_avail) + (sizeof(ph_heap_t) *
+ arena_size = offsetof(arena_t, runs_avail) + (sizeof(arena_run_heap_t) *
runs_avail_nclasses);
/*
* Allocate arena, arena->lstats, and arena->hstats contiguously, mainly
@@ -3372,7 +3371,7 @@ arena_new(unsigned ind)
arena->ndirty = 0;
for(i = 0; i < runs_avail_nclasses; i++)
- ph_new(&arena->runs_avail[i]);
+ arena_run_heap_new(&arena->runs_avail[i]);
qr_new(&arena->runs_dirty, rd_link);
qr_new(&arena->chunks_cache, cc_link);
@@ -3401,7 +3400,7 @@ arena_new(unsigned ind)
if (malloc_mutex_init(&bin->lock))
return (NULL);
bin->runcur = NULL;
- ph_new(&bin->runs);
+ arena_run_heap_new(&bin->runs);
if (config_stats)
memset(&bin->stats, 0, sizeof(malloc_bin_stats_t));
}
diff --git a/src/ph.c b/src/ph.c
deleted file mode 100644
index 051a20d..0000000
--- a/src/ph.c
+++ /dev/null
@@ -1,2 +0,0 @@
-#define JEMALLOC_PH_C_
-#include "jemalloc/internal/jemalloc_internal.h"
diff --git a/test/unit/ph.c b/test/unit/ph.c
index 103475b..da442f0 100644
--- a/test/unit/ph.c
+++ b/test/unit/ph.c
@@ -3,58 +3,275 @@
typedef struct node_s node_t;
struct node_s {
- ph_node_t link;
+#define NODE_MAGIC 0x9823af7e
+ uint32_t magic;
+ phn(node_t) link;
+ uint64_t key;
};
-TEST_BEGIN(test_ph_empty)
+static int
+node_cmp(const node_t *a, const node_t *b)
+{
+ int ret;
+
+ ret = (a->key > b->key) - (a->key < b->key);
+ if (ret == 0) {
+ /*
+ * Duplicates are not allowed in the heap, so force an
+ * arbitrary ordering for non-identical items with equal keys.
+ */
+ ret = (((uintptr_t)a) > ((uintptr_t)b))
+ - (((uintptr_t)a) < ((uintptr_t)b));
+ }
+ return (ret);
+}
+
+static int
+node_cmp_magic(const node_t *a, const node_t *b) {
+
+ assert_u32_eq(a->magic, NODE_MAGIC, "Bad magic");
+ assert_u32_eq(b->magic, NODE_MAGIC, "Bad magic");
+
+ return (node_cmp(a, b));
+}
+
+typedef ph(node_t) heap_t;
+ph_gen(static, heap_, heap_t, node_t, link, node_cmp_magic);
+
+static void
+node_print(const node_t *node, unsigned depth)
+{
+ unsigned i;
+ node_t *leftmost_child, *sibling;
+
+ for (i = 0; i < depth; i++)
+ malloc_printf("\t");
+ malloc_printf("%2"FMTu64"\n", node->key);
+
+ leftmost_child = phn_lchild_get(node_t, link, node);
+ if (leftmost_child == NULL)
+ return;
+ node_print(leftmost_child, depth + 1);
+
+ for (sibling = phn_next_get(node_t, link, leftmost_child); sibling !=
+ NULL; sibling = phn_next_get(node_t, link, sibling)) {
+ node_print(sibling, depth + 1);
+ }
+}
+
+static void
+heap_print(const heap_t *heap)
+{
+ node_t *auxelm;
+
+ malloc_printf("vvv heap %p vvv\n", heap);
+ if (heap->ph_root == NULL)
+ goto label_return;
+
+ node_print(heap->ph_root, 0);
+
+ for (auxelm = phn_next_get(node_t, link, heap->ph_root); auxelm != NULL;
+ auxelm = phn_next_get(node_t, link, auxelm)) {
+ assert_ptr_eq(phn_next_get(node_t, link, phn_prev_get(node_t,
+ link, auxelm)), auxelm,
+ "auxelm's prev doesn't link to auxelm");
+ node_print(auxelm, 0);
+ }
+
+label_return:
+ malloc_printf("^^^ heap %p ^^^\n", heap);
+}
+
+static unsigned
+node_validate(const node_t *node, const node_t *parent)
+{
+ unsigned nnodes = 1;
+ node_t *leftmost_child, *sibling;
+
+ if (parent != NULL) {
+ assert_d_ge(node_cmp_magic(node, parent), 0,
+ "Child is less than parent");
+ }
+
+ leftmost_child = phn_lchild_get(node_t, link, node);
+ if (leftmost_child == NULL)
+ return (nnodes);
+ assert_ptr_eq((void *)phn_prev_get(node_t, link, leftmost_child),
+ (void *)node, "Leftmost child does not link to node");
+ nnodes += node_validate(leftmost_child, node);
+
+ for (sibling = phn_next_get(node_t, link, leftmost_child); sibling !=
+ NULL; sibling = phn_next_get(node_t, link, sibling)) {
+ assert_ptr_eq(phn_next_get(node_t, link, phn_prev_get(node_t,
+ link, sibling)), sibling,
+ "sibling's prev doesn't link to sibling");
+ nnodes += node_validate(sibling, node);
+ }
+ return (nnodes);
+}
+
+static unsigned
+heap_validate(const heap_t *heap)
{
- ph_heap_t heap;
+ unsigned nnodes = 0;
+ node_t *auxelm;
- ph_new(&heap);
+ if (heap->ph_root == NULL)
+ goto label_return;
- assert_ptr_null(ph_first(&heap), "Unexpected node");
+ nnodes += node_validate(heap->ph_root, NULL);
+
+ for (auxelm = phn_next_get(node_t, link, heap->ph_root); auxelm != NULL;
+ auxelm = phn_next_get(node_t, link, auxelm)) {
+ assert_ptr_eq(phn_next_get(node_t, link, phn_prev_get(node_t,
+ link, auxelm)), auxelm,
+ "auxelm's prev doesn't link to auxelm");
+ nnodes += node_validate(auxelm, NULL);
+ }
+
+label_return:
+ if (false)
+ heap_print(heap);
+ return (nnodes);
+}
+
+TEST_BEGIN(test_ph_empty)
+{
+ heap_t heap;
+
+ heap_new(&heap);
+ assert_true(heap_empty(&heap), "Heap should be empty");
+ assert_ptr_null(heap_first(&heap), "Unexpected node");
}
TEST_END
+static void
+node_remove(heap_t *heap, node_t *node)
+{
+
+ heap_remove(heap, node);
+
+ node->magic = 0;
+}
+
+static node_t *
+node_remove_first(heap_t *heap)
+{
+ node_t *node = heap_remove_first(heap);
+ node->magic = 0;
+ return (node);
+}
+
TEST_BEGIN(test_ph_random)
{
#define NNODES 25
+#define NBAGS 250
#define SEED 42
sfmt_t *sfmt;
- ph_heap_t heap;
+ uint64_t bag[NNODES];
+ heap_t heap;
node_t nodes[NNODES];
unsigned i, j, k;
sfmt = init_gen_rand(SEED);
- for (i = 0; i < 2; i++) {
+ for (i = 0; i < NBAGS; i++) {
+ switch (i) {
+ case 0:
+ /* Insert in order. */
+ for (j = 0; j < NNODES; j++)
+ bag[j] = j;
+ break;
+ case 1:
+ /* Insert in reverse order. */
+ for (j = 0; j < NNODES; j++)
+ bag[j] = NNODES - j - 1;
+ break;
+ default:
+ for (j = 0; j < NNODES; j++)
+ bag[j] = gen_rand64_range(sfmt, NNODES);
+ }
+
for (j = 1; j <= NNODES; j++) {
/* Initialize heap and nodes. */
- ph_new(&heap);
+ heap_new(&heap);
+ assert_u_eq(heap_validate(&heap), 0,
+ "Incorrect node count");
+ for (k = 0; k < j; k++) {
+ nodes[k].magic = NODE_MAGIC;
+ nodes[k].key = bag[k];
+ }
/* Insert nodes. */
for (k = 0; k < j; k++) {
- ph_insert(&heap, &nodes[k].link);
-
- assert_ptr_not_null(ph_first(&heap),
- "Heap should not be empty");
+ heap_insert(&heap, &nodes[k]);
+ if (i % 13 == 12) {
+ /* Trigger merging. */
+ assert_ptr_not_null(heap_first(&heap),
+ "Heap should not be empty");
+ }
+ assert_u_eq(heap_validate(&heap), k + 1,
+ "Incorrect node count");
}
+ assert_false(heap_empty(&heap),
+ "Heap should not be empty");
+
/* Remove nodes. */
- switch (i % 2) {
+ switch (i % 4) {
case 0:
- for (k = 0; k < j; k++)
- ph_remove(&heap, &nodes[k].link);
+ for (k = 0; k < j; k++) {
+ assert_u_eq(heap_validate(&heap), j - k,
+ "Incorrect node count");
+ node_remove(&heap, &nodes[k]);
+ assert_u_eq(heap_validate(&heap), j - k
+ - 1, "Incorrect node count");
+ }
break;
case 1:
- for (k = j; k > 0; k--)
- ph_remove(&heap, &nodes[k-1].link);
+ for (k = j; k > 0; k--) {
+ node_remove(&heap, &nodes[k-1]);
+ assert_u_eq(heap_validate(&heap), k - 1,
+ "Incorrect node count");
+ }
break;
- default:
+ case 2: {
+ node_t *prev = NULL;
+ for (k = 0; k < j; k++) {
+ node_t *node = node_remove_first(&heap);
+ assert_u_eq(heap_validate(&heap), j - k
+ - 1, "Incorrect node count");
+ if (prev != NULL) {
+ assert_d_ge(node_cmp(node,
+ prev), 0,
+ "Bad removal order");
+ }
+ prev = node;
+ }
+ break;
+ } case 3: {
+ node_t *prev = NULL;
+ for (k = 0; k < j; k++) {
+ node_t *node = heap_first(&heap);
+ assert_u_eq(heap_validate(&heap), j - k,
+ "Incorrect node count");
+ if (prev != NULL) {
+ assert_d_ge(node_cmp(node,
+ prev), 0,
+ "Bad removal order");
+ }
+ node_remove(&heap, node);
+ assert_u_eq(heap_validate(&heap), j - k
+ - 1, "Incorrect node count");
+ prev = node;
+ }
+ break;
+ } default:
not_reached();
}
- assert_ptr_null(ph_first(&heap),
- "Heap should not be empty");
+ assert_ptr_null(heap_first(&heap),
+ "Heap should be empty");
+ assert_true(heap_empty(&heap), "Heap should be empty");
}
}
fini_gen_rand(sfmt);