]> mj.ucw.cz Git - libucw.git/commitdiff
Heaps: Experiments with Pairing Heaps and Rank Pairing Heaps.
authorPavel Charvat <pchar@ucw.cz>
Sun, 19 Aug 2018 17:11:05 +0000 (19:11 +0200)
committerPavel Charvat <pchar@ucw.cz>
Sun, 19 Aug 2018 17:11:05 +0000 (19:11 +0200)
ucw/Makefile
ucw/heap-bench-gen.h [new file with mode: 0644]
ucw/heap-bench.c [new file with mode: 0644]
ucw/heap-gen.h [new file with mode: 0644]

index dfdc9fbe49ef920175ab605660e2b56ceca15e7e..05fdcbbeb946e3bf2b81e4bbdd1fdb337689954f 100644 (file)
@@ -189,6 +189,9 @@ TESTS+=$(addprefix $(o)/ucw/,asio.test)
 $(o)/ucw/asio.test: $(o)/ucw/asio-t
 endif
 
+$(o)/ucw/heap-bench: $(o)/ucw/heap-bench.o $(LIBUCW)
+PROGS+=$(o)/ucw/heap-bench
+
 # The version of autoconf.h that is a part of the public API needs to have
 # the internal symbols filtered out, so we generate ucw/autoconf.h in the
 # configure script and let the public config.h refer to <ucw/autoconf.h>
diff --git a/ucw/heap-bench-gen.h b/ucw/heap-bench-gen.h
new file mode 100644 (file)
index 0000000..4b36e5e
--- /dev/null
@@ -0,0 +1,179 @@
+#define HEAP_PREFIX(x) TEST_PREFIX(x)
+#if defined(TEST_WANT_SORT)
+#  define HEAP_WANT_INSERT
+#  define HEAP_WANT_DELETE_MIN
+#endif
+#if defined(TEST_WANT_DIJKSTRA)
+#  define HEAP_WANT_INSERT
+#  define HEAP_WANT_DELETE_MIN
+#  define HEAP_WANT_DECREASE
+#endif
+#if defined(TEST_WANT_MIX)
+#  define HEAP_WANT_FIND_MIN
+#  define HEAP_WANT_INSERT
+#  define HEAP_WANT_REMOVE
+#  define HEAP_WANT_DECREASE
+#  define HEAP_WANT_INCREASE
+#endif
+struct TEST_PREFIX(heap);
+struct TEST_PREFIX(node);
+static inline int TEST_PREFIX(less)(struct TEST_PREFIX(heap) *heap, struct TEST_PREFIX(node) *a, struct TEST_PREFIX(node) *b);
+#include <ucw/heap-gen.h>
+
+struct TEST_PREFIX(item) {
+  struct TEST_PREFIX(node) n;
+  intmax_t key;
+};
+
+static inline int TEST_PREFIX(less)(struct TEST_PREFIX(heap) *heap UNUSED, struct TEST_PREFIX(node) *a, struct TEST_PREFIX(node) *b)
+{
+  test.num_cmp++;
+  return ((struct TEST_PREFIX(item) *)a)->key < ((struct TEST_PREFIX(item) *)b)->key;
+}
+
+#define P(x) TEST_PREFIX(x)
+
+#if defined(TEST_WANT_SORT)
+static void
+P(sort)(intmax_t *ary, uint n)
+{
+  test_init(STRINGIFY_EXPANDED(P(sort)));
+  struct P(heap) heap;
+  struct P(item) *items = mp_alloc(test.pool, n * sizeof(*items));
+  for (uint i = 0; i < n; i++)
+    items[i].key = ary[i];
+  start_clock();
+  P(init)(&heap);
+  for (uint i = 0; i < n; i++)
+    P(insert)(&heap, &items[i].n);
+  for (uint i = 0; i < n; i++)
+    {
+      struct P(item) *min = (struct P(item) *)P(delete_min)(&heap);
+      if (!min)
+       ASSERT(0);
+      update_checksum(min->key);
+    }
+  if (P(delete_min)(&heap))
+    ASSERT(0);
+  stop_clock();
+  test_done();
+}
+#endif
+
+#if defined(TEST_WANT_DIJKSTRA)
+static void
+P(dijkstra)(struct graph *g, uint start)
+{
+  test_init(STRINGIFY_EXPANDED(P(dijkstra)));
+  struct P(heap) heap;
+  struct P(item) *items = mp_alloc(test.pool, g->n * sizeof(*items));
+  for (uint i = 0; i < g->n; i++)
+    items[i].key = INTMAX_MAX;
+  items[start].key = 0;
+  start_clock();
+  P(init)(&heap);
+  for (uint v = 0; v < g->n; v++)
+    P(insert)(&heap, &items[v].n);
+  uint num_reachable = 0;
+  uintmax_t num_decrease = 0;
+  uintmax_t max_dist = 0;
+  while (1)
+    {
+      struct P(item) *min = (struct P(item) *)P(delete_min)(&heap);
+      if (!min || min->key == INTMAX_MAX)
+       break;
+      num_reachable++;
+      max_dist = min->key;
+      update_checksum(min->key);
+      uint u = min - items;
+      for (int e = g->v_first_edge[u]; e >= 0; e = g->e_next_edge[e])
+       {
+         uint v = g->e_dest[e];
+         intmax_t d = min->key + g->e_weight[e];
+         if (d < items[v].key)
+           {
+             items[v].key = d;
+             P(decrease)(&heap, &items[v].n);
+             num_decrease++;
+           }
+       }
+    }
+  stop_clock();
+  add_note("decrease:%ju reachable:%u max-dist:%ju", num_decrease, num_reachable, max_dist);
+  test_done();
+}
+#endif
+
+#if defined(TEST_WANT_MIX)
+static void
+P(mix)(uint n, uint m, uint k)
+{
+  test_init(STRINGIFY_EXPANDED(P(mix)));
+  struct eltpool *item_pool = ep_new(sizeof(struct P(item)), 100);;
+  struct P(item) **item_ary;
+  GARY_INIT(item_ary, 0);
+  struct P(heap) heap;
+  uintmax_t num_insert = 0, num_remove = 0, num_increase = 0, num_decrease = 0;
+  start_clock();
+  P(init)(&heap);
+  while (m--)
+    {
+      if (!GARY_SIZE(item_ary))
+       goto force_insert;
+      uint ins_rem_prob = 20;
+      uint ins_prob;
+      if (GARY_SIZE(item_ary) < n)
+       ins_prob = ins_rem_prob / 4 * 3;
+      else
+       ins_prob = ins_rem_prob / 4;
+      int r = random_max(100);
+      if ((r -= ins_prob) < 0)
+       {
+force_insert:
+         num_insert++;
+         struct P(item) *item = ep_alloc(item_pool);
+         item->key = random_max(k);
+         *GARY_PUSH(item_ary) = item;
+         P(insert)(&heap, &item->n);
+       }
+      else
+       {
+         uint i = random_max(GARY_SIZE(item_ary));
+         struct P(item) *item = item_ary[i];
+         if ((r -= ins_rem_prob - ins_prob) < 0)
+           {
+             num_remove++;
+             item_ary[i] = item_ary[GARY_SIZE(item_ary) - 1];
+             GARY_POP(item_ary);
+             P(remove)(&heap, &item->n);
+             ep_free(item_pool, item);
+           }
+         else if ((r -= (100 - ins_rem_prob) / 2) < 0)
+           {
+             num_decrease++;
+             item->key -= (int)random_max(k / 10 + 1) + 1;
+             P(decrease)(&heap, &item->n);
+           }
+         else
+           {
+             num_increase++;
+             item->key += (int)random_max(k / 10 + 1) + 1;
+             P(increase)(&heap, &item->n);
+           }
+       }
+      struct P(item) *min = (struct P(item) *)P(find_min)(&heap);
+      update_checksum(min ? min->key : INTMAX_MIN);
+    }
+  stop_clock();
+  GARY_FREE(item_ary);
+  ep_delete(item_pool);
+  add_note("insert:%ju remove:%ju decrease:%ju increase:%ju", num_insert, num_remove, num_decrease, num_increase);
+  test_done();
+}
+#endif
+
+#undef TEST_PREFIX
+#undef TEST_WANT_SORT
+#undef TEST_WANT_DIJKSTRA
+#undef TEST_WANT_MIX
+#undef P
diff --git a/ucw/heap-bench.c b/ucw/heap-bench.c
new file mode 100644 (file)
index 0000000..0b2264c
--- /dev/null
@@ -0,0 +1,296 @@
+/*
+ *     Tests & benchmarks of various heap data structures
+ *
+ *     (c) 2018 Pavel Charvat <pchar@ucw.cz>
+ */
+
+#include <ucw/lib.h>
+#include <ucw/eltpool.h>
+#include <ucw/gary.h>
+#include <ucw/hashfunc.h>
+#include <ucw/mempool.h>
+#include <ucw/opt.h>
+#include <ucw/simple-lists.h>
+#include <ucw/time.h>
+
+#include <unistd.h>
+#include <sys/types.h>
+
+static int o_seed;
+
+static int o_heap_pairing_noparent;
+static int o_heap_pairing;
+static int o_heap_rank_pairing_noparent;
+static int o_heap_rank_pairing_type1;
+static int o_heap_rank_pairing_type2;
+
+static int o_test_type = -1;
+enum test_type {
+  TT_RANDOM_SORT,
+  TT_RANDOM_DIJKSTRA,
+  TT_RANDOM_MIX,
+};
+
+static uint o_n, o_m, o_k;
+
+static struct test {
+  struct mempool *pool;
+  const char *name;
+  timestamp_t timer;
+  uint time;
+  uint checksum;
+  uintmax_t num_cmp;
+  clist notes;
+} test;
+
+static void test_init(const char *name)
+{
+  ASSERT(!test.pool);
+  bzero(&test, sizeof(test));
+  test.pool = mp_new(4096);
+  test.name = name;
+  clist_init(&test.notes);
+  srand(o_seed);
+}
+
+static inline void update_checksum(uintmax_t val)
+{
+  test.checksum = (test.checksum + val + 17) * 1125899906842597UL;
+}
+
+static void FORMAT_CHECK(printf,1,2) add_note(const char *fmt, ...)
+{
+  va_list va;
+  va_start(va, fmt);
+  simp_append(test.pool, &test.notes)->s = mp_vprintf(test.pool, fmt, va);
+  va_end(va);
+}
+
+static void start_clock(void)
+{
+  init_timer(&test.timer);
+}
+
+static void stop_clock(void)
+{
+  test.time += get_timer(&test.timer);
+}
+
+static void test_done(void)
+{
+  char *notes = mp_printf(test.pool, "sec:%0.3f csum:0x%x cmp:%ju", 0.001 * test.time, test.checksum, test.num_cmp);
+  CLIST_FOR_EACH(simp_node *, n, test.notes)
+    notes = mp_printf_append(test.pool, notes, " %s", n->s);
+  msg(L_INFO, "TEST[%s] -> %s", test.name, notes);
+  mp_delete(test.pool);
+  test.pool = NULL;
+}
+
+struct graph {
+  uint n, m;
+  int *v_first_edge;
+  int *e_next_edge;
+  uint *e_dest;
+  uint *e_weight;
+};
+
+static struct graph *generate_random_graph(uint n, uint m, uint w_min, uint w_max)
+{
+  struct graph *g = xmalloc_zero(sizeof(*g));
+  g->n = n;
+  g->m = m;
+  g->v_first_edge = xmalloc(n * sizeof(*g->v_first_edge));
+  g->e_next_edge = xmalloc(m * sizeof(*g->e_next_edge));
+  g->e_dest = xmalloc(m * sizeof(*g->e_dest));
+  g->e_weight = xmalloc(m * sizeof(*g->e_weight));
+  for (uint v = 0; v < n; v++)
+    g->v_first_edge[v] = -1;
+  for (uint e = 0; e < m; e++)
+    {
+      uint u = random_max(n);
+      uint v = random_max(n);
+      g->e_next_edge[e] = g->v_first_edge[u];
+      g->v_first_edge[u] = e;
+      g->e_dest[e] = v;
+      g->e_weight[e] = w_min + random_max(w_max - w_min + 1);
+    }
+  return g;
+}
+
+static void free_graph(struct graph *g)
+{
+  xfree(g->v_first_edge);
+  xfree(g->e_next_edge);
+  xfree(g->e_dest);
+  xfree(g->e_weight);
+  xfree(g);
+}
+
+#define TEST_PREFIX(x) pairing_noparent_##x
+#define HEAP_PAIRING
+#define TEST_WANT_SORT
+#include <ucw/heap-bench-gen.h>
+
+#define TEST_PREFIX(x) pairing_##x
+#define HEAP_PAIRING
+#define TEST_WANT_SORT
+#define TEST_WANT_DIJKSTRA
+#define TEST_WANT_MIX
+#include <ucw/heap-bench-gen.h>
+
+#define TEST_PREFIX(x) rank_pairing_noparent_##x
+#define HEAP_RANK_PAIRING
+#define HEAP_RANK_PAIRING_TYPE1
+#define TEST_WANT_SORT
+#include <ucw/heap-bench-gen.h>
+
+#define TEST_PREFIX(x) rank_pairing_type1_##x
+#define HEAP_RANK_PAIRING
+#define HEAP_RANK_PAIRING_TYPE1
+#define TEST_WANT_SORT
+#define TEST_WANT_DIJKSTRA
+#define TEST_WANT_MIX
+#include <ucw/heap-bench-gen.h>
+
+#define TEST_PREFIX(x) rank_pairing_type2_##x
+#define HEAP_RANK_PAIRING
+#define HEAP_RANK_PAIRING_TYPE2
+#define TEST_WANT_SORT
+#define TEST_WANT_DIJKSTRA
+#define TEST_WANT_MIX
+#include <ucw/heap-bench-gen.h>
+
+static void FORMAT_CHECK(printf,1,2) test_start(const char *fmt, ...)
+{
+  msg(L_INFO, " ");
+  va_list va;
+  va_start(va, fmt);
+  vmsg(L_INFO, fmt, va);
+  va_end(va);
+  srand(o_seed + 12345);
+}
+
+static void test_random_sort(uint n, uint k)
+{
+  test_start("Testing randomized sort, n=%u k=%u", n, k);
+
+  uintmax_t *ary = xmalloc(n * sizeof(*ary));
+  for (uint i = 0; i < n; i++)
+    ary[i] = random_max(n);
+
+  if (o_heap_pairing_noparent)
+    pairing_noparent_sort(ary, n);
+  if (o_heap_pairing)
+    pairing_sort(ary, n);
+  if (o_heap_rank_pairing_noparent)
+    rank_pairing_noparent_sort(ary, n);
+  if (o_heap_rank_pairing_type1)
+    rank_pairing_type1_sort(ary, n);
+  if (o_heap_rank_pairing_type2)
+    rank_pairing_type2_sort(ary, n);
+
+  xfree(ary);
+}
+
+static void test_random_dijkstra(uint n, uint m, uint k)
+{
+  test_start("Testing randomized dijkstra, n=%u m=%u k=%u", n, m, k);
+
+  ASSERT(k);
+  struct graph *g = generate_random_graph(n, m, 1, k);
+  uint start = random_max(g->n);
+
+  if (o_heap_pairing)
+    pairing_dijkstra(g, start);
+  if (o_heap_rank_pairing_type1)
+    rank_pairing_type1_dijkstra(g, start);
+  if (o_heap_rank_pairing_type2)
+    rank_pairing_type2_dijkstra(g, start);
+
+  free_graph(g);
+}
+
+static void test_random_mix(uint n, uint m, uint k)
+{
+  test_start("Testing randomized mix, n=%u m=%u k=%u", n, m, k);
+
+  if (o_heap_pairing)
+    pairing_mix(n, m, k);
+  if (o_heap_rank_pairing_type1)
+    rank_pairing_type1_mix(n, m, k);
+  if (o_heap_rank_pairing_type2)
+    rank_pairing_type2_mix(n, m, k);
+}
+
+static struct opt_section options = {
+  OPT_ITEMS {
+    OPT_HELP("Usage: heap-bench [<options>] [<heap-type>] [<test-type>]"),
+    OPT_HELP(""),
+    OPT_HELP("Options:"),
+    OPT_HELP_OPTION,
+    OPT_INT('r', "random-seed", o_seed, OPT_REQUIRED_VALUE, "<val>\tUse this random seed (default: generate one)"),
+    OPT_HELP(""),
+    OPT_HELP("Heap types (default: compare all of them):"),
+    OPT_BOOL(0, "pairing-noparent", o_heap_pairing_noparent, 0, "\tPairing heaps with no parent pointers (without Decrease, etc.)"),
+    OPT_BOOL(0, "pairing", o_heap_pairing, 0, "\tPairing heaps"),
+    OPT_BOOL(0, "rank-pairing-noparent", o_heap_rank_pairing_noparent, 0, "\tRank pairing heaps with no parent pointers (without Decrease, etc.)"),
+    OPT_BOOL(0, "rank-pairing-type1", o_heap_rank_pairing_type1, 0, "\tType 1 of rank pairing heaps"),
+    OPT_BOOL(0, "rank-pairing-type2", o_heap_rank_pairing_type2, 0, "\tType 2 of rank pairing heaps"),
+    OPT_HELP(""),
+    OPT_HELP("Test type (default: a mix of various tests):"),
+    OPT_SWITCH(0, "random-sort", o_test_type, TT_RANDOM_SORT, 0, "\tSort a random sequence (options: -n<count>, -k<value-range>)"),
+    OPT_SWITCH(0, "random-dijkstra", o_test_type, TT_RANDOM_DIJKSTRA, 0, "\tFind all nearest paths from a vertex in random graph (options: -n<vertices>, -e<edges>, -k<edge-value-range>)"),
+    OPT_SWITCH(0, "random-mix", o_test_type, TT_RANDOM_MIX, 0, "\tRandomized mix of Insert, Remove, Decrease, Increase (options: -n<avg-nodes>, -e<operations>, -k<initial-value-range>)"),
+    OPT_UINT('n', NULL, o_n, OPT_REQUIRED_VALUE, NULL),
+    OPT_UINT('m', NULL, o_m, OPT_REQUIRED_VALUE, NULL),
+    OPT_UINT('k', NULL, o_k, OPT_REQUIRED_VALUE, NULL),
+    OPT_END
+  }
+};
+
+int main(int argc UNUSED, char **argv)
+{
+  o_seed = (u16)(hash_u32(getpid()) ^ get_timestamp());
+  opt_parse(&options, argv + 1);
+
+  msg(L_DEBUG, "Using random seed %u", o_seed);
+
+  if (1
+    && !o_heap_pairing_noparent
+    && !o_heap_pairing
+    && !o_heap_rank_pairing_noparent
+    && !o_heap_rank_pairing_type1
+    && !o_heap_rank_pairing_type2
+    )
+    {
+      o_heap_pairing_noparent = 1;
+      o_heap_pairing = 1;
+      o_heap_rank_pairing_noparent = 1;
+      o_heap_rank_pairing_type1 = 1;
+      o_heap_rank_pairing_type2 = 1;
+    }
+
+  switch (o_test_type)
+    {
+    case TT_RANDOM_SORT:
+      test_random_sort(o_n ? : 1000000, o_k ? : 1000000000);
+      break;
+
+    case TT_RANDOM_DIJKSTRA:
+      test_random_dijkstra(o_n ? : 300000, o_m ? : 3000000, o_k ? : 1000);
+      break;
+
+    case TT_RANDOM_MIX:
+      test_random_mix(o_n ? : 300000, o_m ? : 5000000, o_k ? : 10000);
+      break;
+
+    default:
+      test_random_sort(1000000, 1000000000);
+      test_random_sort(1000000, 1000);
+      test_random_dijkstra(300000, 3000000, 1000);
+      test_random_mix(300000, 5000000, 10000);
+      break;
+    }
+
+  return 0;
+}
diff --git a/ucw/heap-gen.h b/ucw/heap-gen.h
new file mode 100644 (file)
index 0000000..9b487cb
--- /dev/null
@@ -0,0 +1,497 @@
+/*
+ *     UCW Library -- Heap Data Structures: Code generator
+ *
+ *     (c) 2018 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ *
+ *
+ *     References:
+ *
+ *         [1] Michael L. Fredman, Robert Sedgewick, Daniel D. Sleator, Robert E. Tarjan,
+ *             The pairing heap: A new form of self-adjusting heap,
+ *             Algorithmica, 1(1–4):111–129, 1986.
+ *
+ *          [2]        Bernhard Haeupler, Siddhartha Sen, Robert E. Tarjan,
+ *             Rank-Pairing Heaps,
+ *             SIAM J. Computing, 40(6):1463–1485, 2011.
+ *
+ *         [3] Daniel H. Larkin, Siddhartha Sen, Robert E. Tarjan,
+ *             A Back-to-Basics Empirical Study of Priority Queues,
+ *             2014.
+ */
+
+#define P(x) HEAP_PREFIX(x)
+
+#if defined(HEAP_PAIRING)
+#  if defined(HEAP_WANT_REPLACE)
+#    define HEAP_WANT_REMOVE
+#    define HEAP_WANT_INSERT
+#  endif
+#  if defined(HEAP_WANT_REMOVE) || defined(HEAP_WANT_DECREASE)
+#    define HEAP_NODE_STORE_PARENT
+#  endif
+
+#elif defined(HEAP_RANK_PAIRING)
+#  if !defined(HEAP_RANK_PAIRING_TYPE1) && !defined(HEAP_RANK_PAIRING_TYPE2)
+#    define HEAP_RANK_PAIRING_TYPE1
+#  endif
+#  if defined(HEAP_WANT_INCREASE) || defined(HEAP_WANT_REPLACE)
+#    define HEAP_WANT_REMOVE
+#    define HEAP_WANT_INSERT
+#  endif
+#  define HEAP_NODE_STORE_RANK
+#  if defined(HEAP_WANT_REMOVE) || defined(HEAP_WANT_DECREASE)
+#    define HEAP_NODE_STORE_PARENT
+#  endif
+
+#else
+#  error Missing heap type
+#endif
+
+typedef struct P(heap) *P(heap_p);
+typedef struct P(node) *P(node_p);
+
+struct P(heap) {
+  P(node_p) root;
+};
+
+struct P(node) {
+  P(node_p) left, right;
+#if defined(HEAP_NODE_STORE_PARENT)
+  P(node_p) parent;
+#endif
+#if defined(HEAP_NODE_STORE_RANK)
+  byte rank;
+#endif
+};
+
+static inline void
+P(init)(P(heap_p) h)
+{
+  h->root = NULL;
+}
+
+// @a and @b must be non-NULL roots of two half-trees, function merges them and returns new half-tree.
+// @parent and @right pointers in @a and @b can be undefined, but in resulting root they are not automatically cleared.
+static inline P(node_p)
+P(link)(P(heap_p) h, P(node_p) a, P(node_p) b)
+{
+  P(node_p) c;
+  if (P(less)(h, b, a))
+    {
+      c = a;
+      a = b;
+      b = c;
+    }
+  c = a->left;
+  a->left = b;
+  b->right = c;
+#if defined(HEAP_NODE_STORE_PARENT)
+  b->parent = a;
+  if (c)
+    c->parent = b;
+#endif
+#if defined(HEAP_NODE_STORE_RANK)
+  // "a->rank++" would not work for rank pairing heaps
+  a->rank = b->rank + 1;
+#endif
+  return a;
+}
+
+#if defined(HEAP_PAIRING) && (defined(HEAP_WANT_DELETE_MIN) || defined(HEAP_WANT_REMOVE) || defined(HEAP_WANT_INCREASE))
+// Merges list of half-trees (from decomposed right spine of a half-tree) into one half-tree,
+// using the two-pass pairing algorithm. Empty list is also supported.
+static P(node_p)
+P(two_pass)(P(heap_p) h, P(node_p) a)
+{
+  if (!a)
+    return NULL;
+
+  // First pass: Merge pairs of trees from left to right and create a link list of them
+  P(node_p) l = NULL, b, c;
+  do
+    {
+      b = a->right;
+      if (!b)
+       c = NULL;
+      else
+       {
+         c = b->right;
+         a = P(link)(h, a, b);
+       }
+      a->right = l;
+      l = a;
+    }
+  while (a = c);
+
+  // Second pass: Merge all trees in the link list from right to left
+  a = l;
+  l = l->right;
+  while (l)
+    {
+      b = l;
+      l = l->right;
+      a = P(link)(h, a, b);
+    }
+
+  // Fix undefined pointers in the new root
+  a->right = NULL;
+#if defined(HEAP_NODE_STORE_PARENT)
+  a->parent = NULL;
+#endif
+  return a;
+}
+#endif
+
+#if defined(HEAP_NODE_STORE_PARENT)
+// Removes @x from a right spine. @x must not be root. Does not update @parent and @right pointers of @x.
+static inline void
+P(cut_from_spine)(P(node_p) a)
+{
+  if (a->parent->left == a)
+    a->parent->left = a->right;
+  else
+    a->parent->right = a->right;
+  if (a->right)
+    a->right->parent = a->parent;
+}
+#endif
+
+#if defined(HEAP_RANK_PAIRING) && (defined(HEAP_WANT_DELETE_MIN) || defined(HEAP_WANT_REMOVE))
+static void
+P(cut_root_and_consolidate)(P(heap_p) h, P(node_p) a)
+{
+  P(node_p) min = NULL;
+  P(node_p) list, *list_tail = &list;
+  P(node_p) bucket[32];
+  byte bucket_ary[32];
+  byte bucket_count = 0;
+  uint bucket_mask = 0;
+
+  // We are going to execute very similar code for two lists in a short loop:
+  // 1) The right spine of root->left
+  // 2) Remaining heap's half-trees except the deleted root
+  P(node_p) x = a->left, stop = NULL;
+  while (1)
+    {
+      while (x != stop)
+       {
+         P(node_p) next = x->right;
+         if (!(bucket_mask & (1U << x->rank)))
+           {
+             bucket_mask |= 1U << x->rank;
+             bucket_ary[bucket_count++] = x->rank;
+             bucket[x->rank] = x;
+           }
+         else if (!bucket[x->rank])
+           {
+             bucket[x->rank] = x;
+           }
+         else
+           {
+             P(node_p) y = bucket[x->rank];
+             bucket[x->rank] = NULL;
+             x = P(link)(h, y, x);
+             *list_tail = x;
+             list_tail = &x->right;
+#if defined(HEAP_NODE_STORE_PARENT)
+             x->parent = NULL;
+#endif
+             if (!min || P(less)(h, x, min))
+               min = x;
+           }
+         x = next;
+       }
+      if (stop)
+       break;
+      stop = a;
+      x = a->right;
+    }
+
+  // Process remaining unpaired half-trees in buckets
+  for (byte i = 0; i < bucket_count; i++)
+    {
+      P(node_p) x = bucket[bucket_ary[i]];
+      if (!x)
+       continue;
+      *list_tail = x;
+      list_tail = &x->right;
+#if defined(HEAP_NODE_STORE_PARENT)
+      x->parent = NULL;
+#endif
+      if (!min || P(less)(h, x, min))
+       min = x;
+    }
+
+  // Close the resulting list of half-trees and update minimum
+  *list_tail = list; // list can be undefined here for an empty list, but it's OK
+  h->root = min;
+}
+#endif
+
+#if defined(HEAP_RANK_PAIRING) && (defined(HEAP_WANT_DECREASE) || defined(HEAP_WANT_REMOVE))
+static void
+P(cut_from_spine_and_update_ranks)(P(node_p) a)
+{
+  P(node_p) x = a->parent, y = a->right;
+  if (x->left == a)
+    x->left = y;
+  else
+    x->right = y;
+  s8 rank = -1;
+  if (y)
+    {
+      y->parent = x;
+      rank = y->rank;
+    }
+  while (1)
+    {
+      P(node_p) z = x->parent;
+      if (!z)
+       {
+         x->rank = rank + 1;
+         break;
+       }
+#if defined(HEAP_RANK_PAIRING_TYPE1)
+      if (!x->left || !x->right)
+       rank++;
+      else if (x->left->rank == x->right->rank)
+       rank = x->left->rank + 1;
+      else if (x->left->rank > x->right->rank)
+       rank = x->left->rank;
+      else
+       rank = x->right->rank;
+#else
+      if (!x->left || !x->right)
+       rank = rank > 0 ? rank : rank + 1;
+      else if (x->left->rank >= x->right->rank)
+       rank = x->left->rank + (x->left->rank - x->right->rank <= 1);
+      else
+       rank = x->right->rank + (x->right->rank - x->left->rank <= 1);
+#endif
+      if (x->rank <= (byte)rank)
+       break;
+      x->rank = (byte)rank;
+      y = x;
+      x = z;
+    }
+}
+#endif
+
+#if defined(HEAP_WANT_EMPTY)
+static inline bool
+P(empty)(P(heap_p) h)
+{
+  return h->root != NULL;
+}
+#endif
+
+#if defined(HEAP_WANT_FIND_MIN)
+static inline P(node_p)
+P(find_min)(P(heap_p) h)
+{
+  return h->root;
+}
+#endif
+
+#if defined(HEAP_WANT_INSERT)
+static void
+P(insert)(P(heap_p) h, P(node_p) a)
+{
+#if defined(HEAP_PAIRING)
+  a->left = a->right = NULL;
+#if defined(HEAP_NODE_STORE_PARENT)
+  a->parent = NULL;
+#endif
+  h->root = h->root ? P(link)(h, h->root, a) : a;
+
+#elif defined(HEAP_RANK_PAIRING)
+  a->left = NULL;
+#if defined(HEAP_NODE_STORE_PARENT)
+  a->parent = NULL;
+#endif
+#if defined(HEAP_NODE_STORE_RANK)
+  a->rank = 0;
+#endif
+  P(node_p) b = h->root;
+  if (!b)
+    {
+      a->right = a;
+      h->root = a;
+    }
+  else
+    {
+      a->right = b->right;
+      b->right = a;
+      if (P(less)(h, a, b))
+       h->root = a;
+    }
+#else
+#  error HEAP_WANT_INSERT is not supported for this heap type
+#endif
+}
+#endif
+
+#if defined(HEAP_WANT_MERGE)
+static void
+P(merge)(P(heap_p) h, P(heap_p) g)
+{
+  P(node_p) a = g->root;
+  if (!g)
+    return;
+  g->root = NULL;
+#if defined(HEAP_PAIRING)
+  h->root = h->root ? P(link)(h, h->root, a) : a;
+#elif defined(HEAP_RANK_PAIRING)
+  P(node_p) b = h->root;
+  if (!b)
+    h->root = a;
+  else
+    {
+      P(node_p) c = a->right;
+      a->right = b->right;
+      b->right = c;
+      if (P(less)(h, a, b))
+       h->root = a;
+    }
+#else
+#  error HEAP_WANT_MERGE is not supported for this heap type
+#endif
+}
+#endif
+
+#if defined(HEAP_WANT_DELETE_MIN)
+static P(node_p)
+P(delete_min)(P(heap_p) h)
+{
+  P(node_p) a = h->root;
+  if (!a)
+    return NULL;
+#if defined(HEAP_PAIRING)
+  h->root = P(two_pass)(h, a->left);
+#elif defined(HEAP_RANK_PAIRING)
+  P(cut_root_and_consolidate)(h, a);
+#else
+#  error HEAP_WANT_DELETE_MIN is not supported for this heap type
+#endif
+  return a;
+}
+#endif
+
+#if defined(HEAP_WANT_REMOVE)
+static void
+P(remove)(P(heap_p) h, P(node_p) a)
+{
+#if defined(HEAP_PAIRING)
+  P(node_p) b = P(two_pass)(h, a->left);
+  if (!a->parent)
+    {
+      h->root = b;
+      return;
+    }
+  P(cut_from_spine)(a);
+  if (b)
+    h->root = P(link)(h, h->root, b);
+#elif defined(HEAP_RANK_PAIRING)
+  // Move @x to a root by emulating decrease; we don't need to update heap->root yet
+  if (a->parent)
+    {
+      P(cut_from_spine_and_update_ranks)(a);
+      a->parent = NULL;
+      a->right = h->root->right;
+      h->root->right = a;
+    }
+  P(cut_root_and_consolidate)(h, a);
+#else
+#  error HEAP_WANT_REMOVE is not supported for this heap type
+#endif
+}
+#endif
+
+#if defined(HEAP_WANT_DECREASE)
+static void
+P(decrease)(P(heap_p) h, P(node_p) a)
+{
+#if defined(HEAP_PAIRING)
+  if (!a->parent)
+    return;
+  P(cut_from_spine)(a);
+  a->parent = a->right = NULL;
+  h->root = P(link)(h, h->root, a);
+#elif defined(HEAP_RANK_PAIRING)
+  if (!a->parent)
+    {
+      if (a == h->root)
+       return;
+    }
+  else
+    {
+      P(cut_from_spine_and_update_ranks)(a);
+      a->rank = a->left ? a->left->rank + 1 : 0;
+      a->parent = NULL;
+      a->right = h->root->right;
+      h->root->right = a;
+    }
+  if (P(less)(h, a, h->root))
+    h->root = a;
+#else
+#  error HEAP_WANT_DECREASE is not supported for this heap type
+#endif
+}
+#endif
+
+#if defined(HEAP_WANT_INCREASE)
+static void
+P(increase)(P(heap_p) h, P(node_p) a)
+{
+#if defined(HEAP_PAIRING)
+  if (!a->left)
+    return;
+  P(node_p) b = P(two_pass)(h, a->left);
+  a->left = NULL;
+  h->root = P(link)(h, h->root, b);
+#elif defined(HEAP_RANK_PAIRING)
+  // FIXME: Optimize?
+  P(remove)(h, a);
+  P(insert)(h, a);
+#else
+#  error HEAP_WANT_INCREASE is not supported for this heap type
+#endif
+}
+#endif
+
+#if defined(HEAP_WANT_REPLACE)
+static void
+P(replace)(P(heap_p) h, P(node_p) a)
+{
+#if defined(HEAP_PAIRING) || defined(HEAP_RANK_PAIRING)
+  P(remove)(h, a);
+  P(insert)(h, a);
+#else
+#  error HEAP_WANT_REPLACE is not supported for this heap type
+#endif
+}
+#endif
+
+#undef HEAP_PREFIX
+
+#undef HEAP_PAIRING
+#undef HEAP_RANK_PAIRING
+#undef HEAP_RANK_PAIRING_TYPE1
+#undef HEAP_RANK_PAIRING_TYPE2
+
+#undef HEAP_WANT_EMPTY
+#undef HEAP_WANT_FIND_MIN
+#undef HEAP_WANT_INSERT
+#undef HEAP_WANT_MERGE
+#undef HEAP_WANT_DELETE_MIN
+#undef HEAP_WANT_REMOVE
+#undef HEAP_WANT_DECREASE
+#undef HEAP_WANT_INCREASE
+#undef HEAP_WANT_REPLACE
+
+#undef P
+#undef HEAP_NODE_STORE_PARENT
+#undef HEAP_NODE_STORE_RANK