From 2a0ff8958f9ca6beae88dab6f9431210faa67c9d Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Fri, 5 Apr 2024 16:08:55 +0900
Subject: [PATCH] binaryheap_bench.

---
 contrib/binaryheap_bench/.gitignore           |   4 +
 contrib/binaryheap_bench/Makefile             |  26 +
 .../binaryheap_bench--1.0.sql                 |  24 +
 contrib/binaryheap_bench/binaryheap_bench.c   | 208 ++++++++
 .../binaryheap_bench/binaryheap_bench.control |   5 +
 contrib/binaryheap_bench/meson.build          |  25 +
 contrib/binaryheap_bench/old_binaryheap.c     | 368 ++++++++++++++
 contrib/binaryheap_bench/old_binaryheap.h     |  69 +++
 contrib/binaryheap_bench/xx_binaryheap.c      | 463 ++++++++++++++++++
 contrib/binaryheap_bench/xx_binaryheap.h      |  76 +++
 10 files changed, 1268 insertions(+)
 create mode 100644 contrib/binaryheap_bench/.gitignore
 create mode 100644 contrib/binaryheap_bench/Makefile
 create mode 100644 contrib/binaryheap_bench/binaryheap_bench--1.0.sql
 create mode 100644 contrib/binaryheap_bench/binaryheap_bench.c
 create mode 100644 contrib/binaryheap_bench/binaryheap_bench.control
 create mode 100644 contrib/binaryheap_bench/meson.build
 create mode 100644 contrib/binaryheap_bench/old_binaryheap.c
 create mode 100644 contrib/binaryheap_bench/old_binaryheap.h
 create mode 100644 contrib/binaryheap_bench/xx_binaryheap.c
 create mode 100644 contrib/binaryheap_bench/xx_binaryheap.h

diff --git a/contrib/binaryheap_bench/.gitignore b/contrib/binaryheap_bench/.gitignore
new file mode 100644
index 0000000000..5dcb3ff972
--- /dev/null
+++ b/contrib/binaryheap_bench/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/contrib/binaryheap_bench/Makefile b/contrib/binaryheap_bench/Makefile
new file mode 100644
index 0000000000..d5b1208430
--- /dev/null
+++ b/contrib/binaryheap_bench/Makefile
@@ -0,0 +1,26 @@
+# contrib/binaryheap_bench/Makefile
+
+MODULE_big = binaryheap_bench
+OBJS = \
+	$(WIN32RES) \
+	xx_binaryheap.o \
+	old_binaryheap.o \
+	binaryheap_bench.o
+
+EXTENSION = binaryheap_bench
+DATA = binaryheap_bench--1.0.sql
+PGFILEDESC = "binaryheap_bench"
+
+REGRESS = binaryheap_bench
+TAP_TESTS = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/binaryheap_bench
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/binaryheap_bench/binaryheap_bench--1.0.sql b/contrib/binaryheap_bench/binaryheap_bench--1.0.sql
new file mode 100644
index 0000000000..bc10e0ed57
--- /dev/null
+++ b/contrib/binaryheap_bench/binaryheap_bench--1.0.sql
@@ -0,0 +1,24 @@
+/* contrib/binaryheap_bench/binaryheap_bench--1.1.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION binaryheap_bench" to load this file. \quit
+
+CREATE FUNCTION bench_load(
+indexed bool,
+cnt int8,
+OUT cnt int8,
+OUT load_ms int8,
+OUT xx_load_ms int8,
+OUT old_load_ms int8)
+RETURNS record
+AS 'MODULE_PATHNAME', 'bench_load'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION bench_sift_down(
+cnt int8,
+OUT cnt int8,
+OUT sift_ms int8,
+OUT xx_sift_ms int8)
+RETURNS record
+AS 'MODULE_PATHNAME', 'bench_sift_down'
+LANGUAGE C STRICT;
diff --git a/contrib/binaryheap_bench/binaryheap_bench.c b/contrib/binaryheap_bench/binaryheap_bench.c
new file mode 100644
index 0000000000..5de76fdbb7
--- /dev/null
+++ b/contrib/binaryheap_bench/binaryheap_bench.c
@@ -0,0 +1,208 @@
+/*-------------------------------------------------------------------------
+ *
+ * binaryheap_bench.c
+ *
+ * Copyright (c) 2016-2024, PostgreSQL Global Development Group
+ *
+ *	  contrib/binaryheap_bench/binaryheap_bench.c
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+
+/*
+ * This benchmark tool uses three binary heap implementations.
+ *
+ * "binaryheap" is the current binaryheap implementation in PostgreSQL. That
+ * is, it internally has a hash table to track each node index within the
+ * node array.
+ *
+ * "xx_binaryheap" is based on "binaryheap" but remove the hash table.
+ * Instead, it has each element have its index with in the node array. The
+ * element's index is updated by the callback function, xx_binaryheap_update_index_fn
+ * specified when xx_binaryheap_allocate().
+ *
+ * "old_binaryheap" is the binaryheap implementation before the "indexed" binary
+ * heap changes are made. It neither has a hash table internally nor tracks nodes'
+ * indexes.
+ */
+#include "lib/binaryheap.h"
+#include "xx_binaryheap.h"
+#include "old_binaryheap.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(bench_load);
+PG_FUNCTION_INFO_V1(bench_sift_down);
+
+typedef struct test_elem
+{
+	int64		key;
+	int			index; /* used only for xx_binaryheap */
+} test_elem;
+
+/* comparator for max-heap */
+static int
+test_elem_cmp(Datum a, Datum b, void *arg)
+{
+	test_elem *e1 = (test_elem *) DatumGetPointer(a);
+	test_elem *e2 = (test_elem *) DatumGetPointer(b);
+
+	if (e1->key < e2->key)
+		return -1;
+	else if (e1->key > e2->key)
+		return 1;
+	return 0;
+}
+
+static void
+test_update_index(Datum a, int new_element_index)
+{
+	test_elem *e = (test_elem *) DatumGetPointer(a);
+	e->index = new_element_index;
+}
+
+Datum
+bench_load(PG_FUNCTION_ARGS)
+{
+	bool	indexed = PG_GETARG_BOOL(0);
+	int64	cnt = PG_GETARG_INT64(1);
+	test_elem	*values;
+	binaryheap	*heap;
+	xx_binaryheap *xx_heap;
+	old_binaryheap *old_heap;
+	TupleDesc	tupdesc;
+	TimestampTz start_time, end_time;
+	long		secs;
+	int			usecs;
+	int64		load_ms, xx_load_ms, old_load_ms;
+	Datum	vals[4];
+	bool	nulls[4];
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	/* generate test data */
+	values = (test_elem *) palloc(sizeof(test_elem) * cnt);
+	for (int64 i = 0; i < cnt; i++)
+		values[i].key = i;
+
+	heap = binaryheap_allocate(cnt, test_elem_cmp, indexed, NULL);
+	xx_heap = xx_binaryheap_allocate(cnt, test_elem_cmp, NULL, test_update_index);
+	old_heap = old_binaryheap_allocate(cnt, test_elem_cmp, NULL);
+
+	/* measure load time of binaryheap */
+	start_time = GetCurrentTimestamp();
+	for (int64 i = 0; i < cnt; i++)
+		binaryheap_add(heap, PointerGetDatum(&(values[i])));
+	end_time = GetCurrentTimestamp();
+	TimestampDifference(start_time, end_time, &secs, &usecs);
+	load_ms = secs * 1000 + usecs / 1000;
+
+	/* measure load time of binaryheap */
+	start_time = GetCurrentTimestamp();
+	for (int64 i = 0; i < cnt; i++)
+		xx_binaryheap_add(xx_heap, PointerGetDatum(&(values[i])));
+	end_time = GetCurrentTimestamp();
+	TimestampDifference(start_time, end_time, &secs, &usecs);
+	xx_load_ms = secs * 1000 + usecs / 1000;
+
+	/* measure load time of old_binaryheap */
+	start_time = GetCurrentTimestamp();
+	for (int64 i = 0; i < cnt; i++)
+		old_binaryheap_add(old_heap, PointerGetDatum(&(values[i])));
+	end_time = GetCurrentTimestamp();
+	TimestampDifference(start_time, end_time, &secs, &usecs);
+	old_load_ms = secs * 1000 + usecs / 1000;
+
+	MemSet(nulls, false, sizeof(nulls));
+	vals[0] = Int64GetDatum(cnt);
+	vals[1] = Int64GetDatum(load_ms);
+	vals[2] = Int64GetDatum(xx_load_ms);
+	vals[3] = Int64GetDatum(old_load_ms);
+
+	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, vals, nulls)));
+}
+
+Datum
+bench_sift_down(PG_FUNCTION_ARGS)
+{
+	int64	cnt = PG_GETARG_INT64(0);
+	test_elem	*values;
+	binaryheap	*heap;
+	xx_binaryheap *xx_heap;
+	TupleDesc	tupdesc;
+	TimestampTz start_time, end_time;
+	long		secs;
+	int			usecs;
+	int64		sift_ms, xx_sift_ms;
+	Datum	vals[3];
+	bool	nulls[3];
+	test_elem * e;
+	int64	old_key;
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	/* generate test data */
+	values = (test_elem *) palloc(sizeof(test_elem) * cnt);
+	for (int64 i = 0; i < cnt; i++)
+		values[i].key = i;
+
+	heap = binaryheap_allocate(cnt, test_elem_cmp, true, NULL);
+	xx_heap = xx_binaryheap_allocate(cnt, test_elem_cmp, NULL, test_update_index);
+
+	/*
+	 * test for binaryheap.
+	 *
+	 * 1. load the test data.
+	 * 2. measure the time of sifting down the top node while decreasing the key
+	 */
+	for (int64 i = 0; i < cnt; i++)
+		binaryheap_add(heap, PointerGetDatum(&(values[i])));
+	e = (test_elem *) DatumGetPointer(binaryheap_first(heap));
+	old_key = e->key;
+	start_time = GetCurrentTimestamp();
+	for (int64 i = 0; i < cnt; i++)
+	{
+		e->key--;
+		binaryheap_update_down(heap, PointerGetDatum(e));
+	}
+	end_time = GetCurrentTimestamp();
+	TimestampDifference(start_time, end_time, &secs, &usecs);
+	sift_ms = secs * 1000 + usecs / 1000;
+
+	/* restore the old key */
+	e->key = old_key;
+
+	/*
+	 * test for xx_binaryheap.
+	 *
+	 * 1. load the test data.
+	 * 2. measure the time of sifting down the top node while decreasing the key
+	 */
+	for (int64 i = 0; i < cnt; i++)
+		xx_binaryheap_add(xx_heap, PointerGetDatum(&(values[i])));
+	e = (test_elem *) DatumGetPointer(xx_binaryheap_first(xx_heap));
+	start_time = GetCurrentTimestamp();
+	for (int64 i = 0; i < cnt; i++)
+	{
+		e->key--;
+		xx_binaryheap_update_down(xx_heap, e->index);
+	}
+	end_time = GetCurrentTimestamp();
+	TimestampDifference(start_time, end_time, &secs, &usecs);
+	xx_sift_ms = secs * 1000 + usecs / 1000;
+
+	MemSet(nulls, false, sizeof(nulls));
+	vals[0] = Int64GetDatum(cnt);
+	vals[1] = Int64GetDatum(sift_ms);
+	vals[2] = Int64GetDatum(xx_sift_ms);
+
+	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, vals, nulls)));
+}
diff --git a/contrib/binaryheap_bench/binaryheap_bench.control b/contrib/binaryheap_bench/binaryheap_bench.control
new file mode 100644
index 0000000000..e6a1e190be
--- /dev/null
+++ b/contrib/binaryheap_bench/binaryheap_bench.control
@@ -0,0 +1,5 @@
+# binaryheap_bench extension
+comment = 'benchmark tool for binary heap'
+default_version = '1.0'
+module_pathname = '$libdir/binaryheap_bench'
+relocatable = true
diff --git a/contrib/binaryheap_bench/meson.build b/contrib/binaryheap_bench/meson.build
new file mode 100644
index 0000000000..64cc7d3687
--- /dev/null
+++ b/contrib/binaryheap_bench/meson.build
@@ -0,0 +1,25 @@
+# Copyright (c) 2022-2024, PostgreSQL Global Development Group
+
+binaryheap_bench_sources = files(
+  'binaryheap_bench.c',
+  'xx_binaryheap.c',
+  'old_binaryheap.c',
+)
+
+if host_system == 'windows'
+  binaryheap_bench_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'binaryheap_bench',
+    '--FILEDESC', 'binaryheap_bench',])
+endif
+
+binaryheap_bench = shared_module('binaryheap_bench',
+  binaryheap_bench_sources,
+  kwargs: contrib_mod_args,
+)
+contrib_targets += binaryheap_bench
+
+install_data(
+  'binaryheap_bench--1.0',
+  'binaryheap_bench.control',
+  kwargs: contrib_data_args,
+)
diff --git a/contrib/binaryheap_bench/old_binaryheap.c b/contrib/binaryheap_bench/old_binaryheap.c
new file mode 100644
index 0000000000..78bdcc63a7
--- /dev/null
+++ b/contrib/binaryheap_bench/old_binaryheap.c
@@ -0,0 +1,368 @@
+/*-------------------------------------------------------------------------
+ *
+ * old_binaryheap.c
+ *	  A simple binary heap implementation
+ *
+ * Portions Copyright (c) 2012-2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/common/old_binaryheap.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifdef FRONTEND
+#include "postgres_fe.h"
+#else
+#include "postgres.h"
+#endif
+
+#include <math.h>
+
+#ifdef FRONTEND
+#include "common/logging.h"
+#endif
+#include "old_binaryheap.h"
+
+static void sift_down(old_binaryheap *heap, int node_off);
+static void sift_up(old_binaryheap *heap, int node_off);
+
+/*
+ * old_binaryheap_allocate
+ *
+ * Returns a pointer to a newly-allocated heap with the given initial number
+ * of nodes, and with the heap property defined by the given comparator
+ * function, which will be invoked with the additional argument specified by
+ * 'arg'.
+ */
+old_binaryheap *
+old_binaryheap_allocate(int num_nodes, old_binaryheap_comparator compare, void *arg)
+{
+	old_binaryheap *heap;
+
+	heap = (old_binaryheap *) palloc(sizeof(old_binaryheap));
+	heap->bh_space = num_nodes;
+	heap->bh_compare = compare;
+	heap->bh_arg = arg;
+
+	heap->bh_size = 0;
+	heap->bh_has_heap_property = true;
+	heap->bh_nodes = (bh_node_type *) palloc(sizeof(bh_node_type) * num_nodes);
+
+	return heap;
+}
+
+/*
+ * old_binaryheap_reset
+ *
+ * Resets the heap to an empty state, losing its data content but not the
+ * parameters passed at allocation.
+ */
+void
+old_binaryheap_reset(old_binaryheap *heap)
+{
+	heap->bh_size = 0;
+	heap->bh_has_heap_property = true;
+}
+
+/*
+ * old_binaryheap_free
+ *
+ * Releases memory used by the given old_binaryheap.
+ */
+void
+old_binaryheap_free(old_binaryheap *heap)
+{
+	pfree(heap->bh_nodes);
+	pfree(heap);
+}
+
+/*
+ * These utility functions return the offset of the left child, right
+ * child, and parent of the node at the given index, respectively.
+ *
+ * The heap is represented as an array of nodes, with the root node
+ * stored at index 0. The left child of node i is at index 2*i+1, and
+ * the right child at 2*i+2. The parent of node i is at index (i-1)/2.
+ */
+
+static inline int
+left_offset(int i)
+{
+	return 2 * i + 1;
+}
+
+static inline int
+right_offset(int i)
+{
+	return 2 * i + 2;
+}
+
+static inline int
+parent_offset(int i)
+{
+	return (i - 1) / 2;
+}
+
+/*
+ * Double the space allocated for nodes.
+ */
+static void
+enlarge_node_array(old_binaryheap *heap)
+{
+	heap->bh_space *= 2;
+	heap->bh_nodes = repalloc(heap->bh_nodes,
+							  sizeof(bh_node_type) * heap->bh_space);
+}
+
+/*
+ * old_binaryheap_add_unordered
+ *
+ * Adds the given datum to the end of the heap's list of nodes in O(1) without
+ * preserving the heap property. This is a convenience to add elements quickly
+ * to a new heap. To obtain a valid heap, one must call old_binaryheap_build()
+ * afterwards.
+ */
+void
+old_binaryheap_add_unordered(old_binaryheap *heap, bh_node_type d)
+{
+	/* make sure enough space for a new node */
+	if (heap->bh_size >= heap->bh_space)
+		enlarge_node_array(heap);
+
+	heap->bh_has_heap_property = false;
+	heap->bh_nodes[heap->bh_size] = d;
+	heap->bh_size++;
+}
+
+/*
+ * old_binaryheap_build
+ *
+ * Assembles a valid heap in O(n) from the nodes added by
+ * old_binaryheap_add_unordered(). Not needed otherwise.
+ */
+void
+old_binaryheap_build(old_binaryheap *heap)
+{
+	int			i;
+
+	for (i = parent_offset(heap->bh_size - 1); i >= 0; i--)
+		sift_down(heap, i);
+	heap->bh_has_heap_property = true;
+}
+
+/*
+ * old_binaryheap_add
+ *
+ * Adds the given datum to the heap in O(log n) time, while preserving
+ * the heap property.
+ */
+void
+old_binaryheap_add(old_binaryheap *heap, bh_node_type d)
+{
+	/* make sure enough space for a new node */
+	if (heap->bh_size >= heap->bh_space)
+		enlarge_node_array(heap);
+
+	heap->bh_nodes[heap->bh_size] = d;
+	heap->bh_size++;
+	sift_up(heap, heap->bh_size - 1);
+}
+
+/*
+ * old_binaryheap_first
+ *
+ * Returns a pointer to the first (root, topmost) node in the heap
+ * without modifying the heap. The caller must ensure that this
+ * routine is not used on an empty heap. Always O(1).
+ */
+bh_node_type
+old_binaryheap_first(old_binaryheap *heap)
+{
+	Assert(!old_binaryheap_empty(heap) && heap->bh_has_heap_property);
+	return heap->bh_nodes[0];
+}
+
+/*
+ * old_binaryheap_remove_first
+ *
+ * Removes the first (root, topmost) node in the heap and returns a
+ * pointer to it after rebalancing the heap. The caller must ensure
+ * that this routine is not used on an empty heap. O(log n) worst
+ * case.
+ */
+bh_node_type
+old_binaryheap_remove_first(old_binaryheap *heap)
+{
+	bh_node_type result;
+
+	Assert(!old_binaryheap_empty(heap) && heap->bh_has_heap_property);
+
+	/* extract the root node, which will be the result */
+	result = heap->bh_nodes[0];
+
+	/* easy if heap contains one element */
+	if (heap->bh_size == 1)
+	{
+		heap->bh_size--;
+		return result;
+	}
+
+	/*
+	 * Remove the last node, placing it in the vacated root entry, and sift
+	 * the new root node down to its correct position.
+	 */
+	heap->bh_nodes[0] = heap->bh_nodes[--heap->bh_size];
+	sift_down(heap, 0);
+
+	return result;
+}
+
+/*
+ * old_binaryheap_remove_node
+ *
+ * Removes the nth (zero based) node from the heap.  The caller must ensure
+ * that there are at least (n + 1) nodes in the heap.  O(log n) worst case.
+ */
+void
+old_binaryheap_remove_node(old_binaryheap *heap, int n)
+{
+	int			cmp;
+
+	Assert(!old_binaryheap_empty(heap) && heap->bh_has_heap_property);
+	Assert(n >= 0 && n < heap->bh_size);
+
+	/* compare last node to the one that is being removed */
+	cmp = heap->bh_compare(heap->bh_nodes[--heap->bh_size],
+						   heap->bh_nodes[n],
+						   heap->bh_arg);
+
+	/* remove the last node, placing it in the vacated entry */
+	heap->bh_nodes[n] = heap->bh_nodes[heap->bh_size];
+
+	/* sift as needed to preserve the heap property */
+	if (cmp > 0)
+		sift_up(heap, n);
+	else if (cmp < 0)
+		sift_down(heap, n);
+}
+
+/*
+ * old_binaryheap_replace_first
+ *
+ * Replace the topmost element of a non-empty heap, preserving the heap
+ * property.  O(1) in the best case, or O(log n) if it must fall back to
+ * sifting the new node down.
+ */
+void
+old_binaryheap_replace_first(old_binaryheap *heap, bh_node_type d)
+{
+	Assert(!old_binaryheap_empty(heap) && heap->bh_has_heap_property);
+
+	heap->bh_nodes[0] = d;
+
+	if (heap->bh_size > 1)
+		sift_down(heap, 0);
+}
+
+/*
+ * Sift a node up to the highest position it can hold according to the
+ * comparator.
+ */
+static void
+sift_up(old_binaryheap *heap, int node_off)
+{
+	bh_node_type node_val = heap->bh_nodes[node_off];
+
+	/*
+	 * Within the loop, the node_off'th array entry is a "hole" that
+	 * notionally holds node_val, but we don't actually store node_val there
+	 * till the end, saving some unnecessary data copying steps.
+	 */
+	while (node_off != 0)
+	{
+		int			cmp;
+		int			parent_off;
+		bh_node_type parent_val;
+
+		/*
+		 * If this node is smaller than its parent, the heap condition is
+		 * satisfied, and we're done.
+		 */
+		parent_off = parent_offset(node_off);
+		parent_val = heap->bh_nodes[parent_off];
+		cmp = heap->bh_compare(node_val,
+							   parent_val,
+							   heap->bh_arg);
+		if (cmp <= 0)
+			break;
+
+		/*
+		 * Otherwise, swap the parent value with the hole, and go on to check
+		 * the node's new parent.
+		 */
+		heap->bh_nodes[node_off] = parent_val;
+		node_off = parent_off;
+	}
+	/* Re-fill the hole */
+	heap->bh_nodes[node_off] = node_val;
+}
+
+/*
+ * Sift a node down from its current position to satisfy the heap
+ * property.
+ */
+static void
+sift_down(old_binaryheap *heap, int node_off)
+{
+	bh_node_type node_val = heap->bh_nodes[node_off];
+
+	/*
+	 * Within the loop, the node_off'th array entry is a "hole" that
+	 * notionally holds node_val, but we don't actually store node_val there
+	 * till the end, saving some unnecessary data copying steps.
+	 */
+	while (true)
+	{
+		int			left_off = left_offset(node_off);
+		int			right_off = right_offset(node_off);
+		int			swap_off = 0;
+
+		/* Is the left child larger than the parent? */
+		if (left_off < heap->bh_size &&
+			heap->bh_compare(node_val,
+							 heap->bh_nodes[left_off],
+							 heap->bh_arg) < 0)
+			swap_off = left_off;
+
+		/* Is the right child larger than the parent? */
+		if (right_off < heap->bh_size &&
+			heap->bh_compare(node_val,
+							 heap->bh_nodes[right_off],
+							 heap->bh_arg) < 0)
+		{
+			/* swap with the larger child */
+			if (!swap_off ||
+				heap->bh_compare(heap->bh_nodes[left_off],
+								 heap->bh_nodes[right_off],
+								 heap->bh_arg) < 0)
+				swap_off = right_off;
+		}
+
+		/*
+		 * If we didn't find anything to swap, the heap condition is
+		 * satisfied, and we're done.
+		 */
+		if (!swap_off)
+			break;
+
+		/*
+		 * Otherwise, swap the hole with the child that violates the heap
+		 * property; then go on to check its children.
+		 */
+		heap->bh_nodes[node_off] = heap->bh_nodes[swap_off];
+		node_off = swap_off;
+	}
+	/* Re-fill the hole */
+	heap->bh_nodes[node_off] = node_val;
+}
diff --git a/contrib/binaryheap_bench/old_binaryheap.h b/contrib/binaryheap_bench/old_binaryheap.h
new file mode 100644
index 0000000000..23c564bfbd
--- /dev/null
+++ b/contrib/binaryheap_bench/old_binaryheap.h
@@ -0,0 +1,69 @@
+/*
+ * old_binaryheap.h
+ *
+ * A simple binary heap implementation
+ *
+ * Portions Copyright (c) 2012-2024, PostgreSQL Global Development Group
+ *
+ * src/include/lib/old_binaryheap.h
+ */
+
+#ifndef OLD_BINARYHEAP_H
+#define OLD_BINARYHEAP_H
+
+/*
+ * We provide a Datum-based API for backend code and a void *-based API for
+ * frontend code (since the Datum definitions are not available to frontend
+ * code).  You should typically avoid using bh_node_type directly and instead
+ * use Datum or void * as appropriate.
+ */
+#ifdef FRONTEND
+typedef void *bh_node_type;
+#else
+typedef Datum bh_node_type;
+#endif
+
+/*
+ * For a max-heap, the comparator must return <0 iff a < b, 0 iff a == b,
+ * and >0 iff a > b.  For a min-heap, the conditions are reversed.
+ */
+typedef int (*old_binaryheap_comparator) (bh_node_type a, bh_node_type b, void *arg);
+
+/*
+ * old_binaryheap
+ *
+ *		bh_size			how many nodes are currently in "nodes"
+ *		bh_space		how many nodes can be stored in "nodes"
+ *		bh_has_heap_property	no unordered operations since last heap build
+ *		bh_compare		comparison function to define the heap property
+ *		bh_arg			user data for comparison function
+ *		bh_nodes		variable-length array of "space" nodes
+ */
+typedef struct old_binaryheap
+{
+	int			bh_size;
+	int			bh_space;
+	bool		bh_has_heap_property;	/* debugging cross-check */
+	old_binaryheap_comparator bh_compare;
+	void	   *bh_arg;
+	bh_node_type *bh_nodes;
+} old_binaryheap;
+
+extern old_binaryheap *old_binaryheap_allocate(int num_nodes,
+									   old_binaryheap_comparator compare,
+									   void *arg);
+extern void old_binaryheap_reset(old_binaryheap *heap);
+extern void old_binaryheap_free(old_binaryheap *heap);
+extern void old_binaryheap_add_unordered(old_binaryheap *heap, bh_node_type d);
+extern void old_binaryheap_build(old_binaryheap *heap);
+extern void old_binaryheap_add(old_binaryheap *heap, bh_node_type d);
+extern bh_node_type old_binaryheap_first(old_binaryheap *heap);
+extern bh_node_type old_binaryheap_remove_first(old_binaryheap *heap);
+extern void old_binaryheap_remove_node(old_binaryheap *heap, int n);
+extern void old_binaryheap_replace_first(old_binaryheap *heap, bh_node_type d);
+
+#define old_binaryheap_empty(h)			((h)->bh_size == 0)
+#define old_binaryheap_size(h)			((h)->bh_size)
+#define old_binaryheap_get_node(h, n)	((h)->bh_nodes[n])
+
+#endif							/* OLD_BINARYHEAP_H */
diff --git a/contrib/binaryheap_bench/xx_binaryheap.c b/contrib/binaryheap_bench/xx_binaryheap.c
new file mode 100644
index 0000000000..41e4ed1549
--- /dev/null
+++ b/contrib/binaryheap_bench/xx_binaryheap.c
@@ -0,0 +1,463 @@
+/*-------------------------------------------------------------------------
+ *
+ * xx_binaryheap.c
+ *	  A simple binary heap implementation
+ *
+ * Portions Copyright (c) 2012-2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/common/xx_binaryheap.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifdef FRONTEND
+#include "postgres_fe.h"
+#else
+#include "postgres.h"
+#endif
+
+#include <math.h>
+
+#ifdef FRONTEND
+#include "common/logging.h"
+#endif
+#include "common/hashfn.h"
+#include "xx_binaryheap.h"
+
+static void sift_down(xx_binaryheap *heap, int node_off);
+static void sift_up(xx_binaryheap *heap, int node_off);
+
+/*
+ * xx_binaryheap_allocate
+ *
+ * Returns a pointer to a newly-allocated heap with the given initial number
+ * of nodes, and with the heap property defined by the given comparator
+ * function, which will be invoked with the additional argument specified by
+ * 'arg'.
+ *
+ * If 'indexed' is true, we create a hash table to track each node's
+ * index in the heap, enabling to perform some operations such as
+ * xx_binaryheap_remove_node_ptr() etc.
+ */
+xx_binaryheap *
+xx_binaryheap_allocate(int num_nodes, xx_binaryheap_comparator compare,
+					   void *arg, xx_binaryheap_update_index_fn update_index)
+{
+	xx_binaryheap *heap;
+
+	heap = (xx_binaryheap *) palloc(sizeof(xx_binaryheap));
+	heap->bh_space = num_nodes;
+	heap->bh_compare = compare;
+	heap->bh_arg = arg;
+
+	heap->bh_size = 0;
+	heap->bh_has_heap_property = true;
+	heap->bh_nodes = (bh_node_type *) palloc(sizeof(bh_node_type) * num_nodes);
+	heap->bh_update_index = update_index;
+
+	return heap;
+}
+
+/*
+ * xx_binaryheap_reset
+ *
+ * Resets the heap to an empty state, losing its data content but not the
+ * parameters passed at allocation.
+ */
+void
+xx_binaryheap_reset(xx_binaryheap *heap)
+{
+	heap->bh_size = 0;
+	heap->bh_has_heap_property = true;
+}
+
+/*
+ * xx_binaryheap_free
+ *
+ * Releases memory used by the given xx_binaryheap.
+ */
+void
+xx_binaryheap_free(xx_binaryheap *heap)
+{
+	pfree(heap->bh_nodes);
+	pfree(heap);
+}
+
+/*
+ * These utility functions return the offset of the left child, right
+ * child, and parent of the node at the given index, respectively.
+ *
+ * The heap is represented as an array of nodes, with the root node
+ * stored at index 0. The left child of node i is at index 2*i+1, and
+ * the right child at 2*i+2. The parent of node i is at index (i-1)/2.
+ */
+
+static inline int
+left_offset(int i)
+{
+	return 2 * i + 1;
+}
+
+static inline int
+right_offset(int i)
+{
+	return 2 * i + 2;
+}
+
+static inline int
+parent_offset(int i)
+{
+	return (i - 1) / 2;
+}
+
+/*
+ * Double the space allocated for nodes.
+ */
+static void
+enlarge_node_array(xx_binaryheap *heap)
+{
+	heap->bh_space *= 2;
+	heap->bh_nodes = repalloc(heap->bh_nodes,
+							  sizeof(bh_node_type) * heap->bh_space);
+}
+
+/*
+ * Set the given node at the 'index' and track it if required.
+ *
+ * Return true if the node's index is already tracked.
+ */
+static inline void
+set_node(xx_binaryheap *heap, bh_node_type node, int index)
+{
+	/* Set the node to the nodes array */
+	heap->bh_nodes[index] = node;
+
+	/* Keep track of the node index */
+	if (xx_binaryheap_indexed(heap))
+		heap->bh_update_index(node, index);
+}
+
+/*
+ * Remove the node's index from the hash table if the heap is indexed.
+ */
+static inline void
+delete_nodeidx(xx_binaryheap *heap, bh_node_type node)
+{
+	/* XXX how we handle the removal in terms of the index? */
+	if (xx_binaryheap_indexed(heap))
+		heap->bh_update_index(node, -1);
+}
+
+/*
+ * Replace the existing node at 'idx' with the given 'new_node'. Also
+ * update their positions accordingly. Note that we assume the new_node's
+ * position is already tracked if enabled, i.e. the new_node is already
+ * present in the heap.
+ */
+static inline void
+replace_node(xx_binaryheap *heap, int index, bh_node_type new_node)
+{
+	bool		found PG_USED_FOR_ASSERTS_ONLY;
+
+	/* Quick return if not necessary to move */
+	if (heap->bh_nodes[index] == new_node)
+		return;
+
+	/* Remove the overwritten node's index */
+	delete_nodeidx(heap, heap->bh_nodes[index]);
+
+	/*
+	 * Replace it with the given new node. This node's position must also be
+	 * tracked as we assume to replace the node with the existing node.
+	 */
+	set_node(heap, new_node, index);
+}
+
+/*
+ * xx_binaryheap_add_unordered
+ *
+ * Adds the given datum to the end of the heap's list of nodes in O(1) without
+ * preserving the heap property. This is a convenience to add elements quickly
+ * to a new heap. To obtain a valid heap, one must call xx_binaryheap_build()
+ * afterwards.
+ */
+void
+xx_binaryheap_add_unordered(xx_binaryheap *heap, bh_node_type d)
+{
+	/* make sure enough space for a new node */
+	if (heap->bh_size >= heap->bh_space)
+		enlarge_node_array(heap);
+
+	heap->bh_has_heap_property = false;
+	set_node(heap, d, heap->bh_size);
+	heap->bh_size++;
+}
+
+/*
+ * xx_binaryheap_build
+ *
+ * Assembles a valid heap in O(n) from the nodes added by
+ * xx_binaryheap_add_unordered(). Not needed otherwise.
+ */
+void
+xx_binaryheap_build(xx_binaryheap *heap)
+{
+	int			i;
+
+	for (i = parent_offset(heap->bh_size - 1); i >= 0; i--)
+		sift_down(heap, i);
+	heap->bh_has_heap_property = true;
+}
+
+/*
+ * xx_binaryheap_add
+ *
+ * Adds the given datum to the heap in O(log n) time, while preserving
+ * the heap property.
+ */
+void
+xx_binaryheap_add(xx_binaryheap *heap, bh_node_type d)
+{
+	/* make sure enough space for a new node */
+	if (heap->bh_size >= heap->bh_space)
+		enlarge_node_array(heap);
+
+	set_node(heap, d, heap->bh_size);
+	heap->bh_size++;
+	sift_up(heap, heap->bh_size - 1);
+}
+
+/*
+ * xx_binaryheap_first
+ *
+ * Returns a pointer to the first (root, topmost) node in the heap
+ * without modifying the heap. The caller must ensure that this
+ * routine is not used on an empty heap. Always O(1).
+ */
+bh_node_type
+xx_binaryheap_first(xx_binaryheap *heap)
+{
+	Assert(!xx_binaryheap_empty(heap) && heap->bh_has_heap_property);
+	return heap->bh_nodes[0];
+}
+
+/*
+ * xx_binaryheap_remove_first
+ *
+ * Removes the first (root, topmost) node in the heap and returns a
+ * pointer to it after rebalancing the heap. The caller must ensure
+ * that this routine is not used on an empty heap. O(log n) worst
+ * case.
+ */
+bh_node_type
+xx_binaryheap_remove_first(xx_binaryheap *heap)
+{
+	bh_node_type result;
+
+	Assert(!xx_binaryheap_empty(heap) && heap->bh_has_heap_property);
+
+	/* extract the root node, which will be the result */
+	result = heap->bh_nodes[0];
+
+	/* easy if heap contains one element */
+	if (heap->bh_size == 1)
+	{
+		heap->bh_size--;
+		delete_nodeidx(heap, result);
+
+		return result;
+	}
+
+	/*
+	 * Remove the last node, placing it in the vacated root entry, and sift
+	 * the new root node down to its correct position.
+	 */
+	replace_node(heap, 0, heap->bh_nodes[--heap->bh_size]);
+	sift_down(heap, 0);
+
+	return result;
+}
+
+/*
+ * xx_binaryheap_remove_node
+ *
+ * Removes the nth (zero based) node from the heap.  The caller must ensure
+ * that there are at least (n + 1) nodes in the heap.  O(log n) worst case.
+ */
+void
+xx_binaryheap_remove_node(xx_binaryheap *heap, int n)
+{
+	int			cmp;
+
+	Assert(!xx_binaryheap_empty(heap) && heap->bh_has_heap_property);
+	Assert(n >= 0 && n < heap->bh_size);
+
+	/* compare last node to the one that is being removed */
+	cmp = heap->bh_compare(heap->bh_nodes[--heap->bh_size],
+						   heap->bh_nodes[n],
+						   heap->bh_arg);
+
+	/* remove the last node, placing it in the vacated entry */
+	replace_node(heap, n, heap->bh_nodes[heap->bh_size]);
+
+	/* sift as needed to preserve the heap property */
+	if (cmp > 0)
+		sift_up(heap, n);
+	else if (cmp < 0)
+		sift_down(heap, n);
+}
+
+/*
+ * xx_binaryheap_update_up
+ *
+ * Sift the given node up after the node's key is updated. The caller must
+ * ensure that the given node is in the heap. O(log n) worst case.
+ *
+ * This function can be used only if the heap is indexed.
+ */
+void
+xx_binaryheap_update_up(xx_binaryheap *heap, int index)
+{
+	Assert(!xx_binaryheap_empty(heap) && heap->bh_has_heap_property);
+	Assert(xx_binaryheap_indexed(heap));
+
+	sift_up(heap, index);
+}
+
+/*
+ * xx_binaryheap_update_down
+ *
+ * Sift the given node down after the node's key is updated. The caller must
+ * ensure that the given node is in the heap. O(log n) worst case.
+ *
+ * This function can be used only if the heap is indexed.
+ */
+void
+xx_binaryheap_update_down(xx_binaryheap *heap, int index)
+{
+	Assert(!xx_binaryheap_empty(heap) && heap->bh_has_heap_property);
+	Assert(xx_binaryheap_indexed(heap));
+
+	sift_down(heap, index);
+}
+
+/*
+ * xx_binaryheap_replace_first
+ *
+ * Replace the topmost element of a non-empty heap, preserving the heap
+ * property.  O(1) in the best case, or O(log n) if it must fall back to
+ * sifting the new node down.
+ */
+void
+xx_binaryheap_replace_first(xx_binaryheap *heap, bh_node_type d)
+{
+	Assert(!xx_binaryheap_empty(heap) && heap->bh_has_heap_property);
+
+	replace_node(heap, 0, d);
+
+	if (heap->bh_size > 1)
+		sift_down(heap, 0);
+}
+
+/*
+ * Sift a node up to the highest position it can hold according to the
+ * comparator.
+ */
+static void
+sift_up(xx_binaryheap *heap, int node_off)
+{
+	bh_node_type node_val = heap->bh_nodes[node_off];
+
+	/*
+	 * Within the loop, the node_off'th array entry is a "hole" that
+	 * notionally holds node_val, but we don't actually store node_val there
+	 * till the end, saving some unnecessary data copying steps.
+	 */
+	while (node_off != 0)
+	{
+		int			cmp;
+		int			parent_off;
+		bh_node_type parent_val;
+
+		/*
+		 * If this node is smaller than its parent, the heap condition is
+		 * satisfied, and we're done.
+		 */
+		parent_off = parent_offset(node_off);
+		parent_val = heap->bh_nodes[parent_off];
+		cmp = heap->bh_compare(node_val,
+							   parent_val,
+							   heap->bh_arg);
+		if (cmp <= 0)
+			break;
+
+		/*
+		 * Otherwise, swap the parent value with the hole, and go on to check
+		 * the node's new parent.
+		 */
+		set_node(heap, parent_val, node_off);
+		node_off = parent_off;
+	}
+	/* Re-fill the hole */
+	set_node(heap, node_val, node_off);
+}
+
+/*
+ * Sift a node down from its current position to satisfy the heap
+ * property.
+ */
+static void
+sift_down(xx_binaryheap *heap, int node_off)
+{
+	bh_node_type node_val = heap->bh_nodes[node_off];
+
+	/*
+	 * Within the loop, the node_off'th array entry is a "hole" that
+	 * notionally holds node_val, but we don't actually store node_val there
+	 * till the end, saving some unnecessary data copying steps.
+	 */
+	while (true)
+	{
+		int			left_off = left_offset(node_off);
+		int			right_off = right_offset(node_off);
+		int			swap_off = 0;
+
+		/* Is the left child larger than the parent? */
+		if (left_off < heap->bh_size &&
+			heap->bh_compare(node_val,
+							 heap->bh_nodes[left_off],
+							 heap->bh_arg) < 0)
+			swap_off = left_off;
+
+		/* Is the right child larger than the parent? */
+		if (right_off < heap->bh_size &&
+			heap->bh_compare(node_val,
+							 heap->bh_nodes[right_off],
+							 heap->bh_arg) < 0)
+		{
+			/* swap with the larger child */
+			if (!swap_off ||
+				heap->bh_compare(heap->bh_nodes[left_off],
+								 heap->bh_nodes[right_off],
+								 heap->bh_arg) < 0)
+				swap_off = right_off;
+		}
+
+		/*
+		 * If we didn't find anything to swap, the heap condition is
+		 * satisfied, and we're done.
+		 */
+		if (!swap_off)
+			break;
+
+		/*
+		 * Otherwise, swap the hole with the child that violates the heap
+		 * property; then go on to check its children.
+		 */
+		set_node(heap, heap->bh_nodes[swap_off], node_off);
+		node_off = swap_off;
+	}
+	/* Re-fill the hole */
+	set_node(heap, node_val, node_off);
+}
diff --git a/contrib/binaryheap_bench/xx_binaryheap.h b/contrib/binaryheap_bench/xx_binaryheap.h
new file mode 100644
index 0000000000..ac7851557f
--- /dev/null
+++ b/contrib/binaryheap_bench/xx_binaryheap.h
@@ -0,0 +1,76 @@
+/*
+ * xx_binaryheap.h
+ *
+ * A simple binary heap implementation
+ *
+ * Portions Copyright (c) 2012-2024, PostgreSQL Global Development Group
+ *
+ * src/include/lib/xx_binaryheap.h
+ */
+
+#ifndef XX_BINARYHEAP_H
+#define XX_BINARYHEAP_H
+
+/*
+ * We provide a Datum-based API for backend code and a void *-based API for
+ * frontend code (since the Datum definitions are not available to frontend
+ * code).  You should typically avoid using bh_node_type directly and instead
+ * use Datum or void * as appropriate.
+ */
+#ifdef FRONTEND
+typedef void *bh_node_type;
+#else
+typedef Datum bh_node_type;
+#endif
+
+/*
+ * For a max-heap, the comparator must return <0 iff a < b, 0 iff a == b,
+ * and >0 iff a > b.  For a min-heap, the conditions are reversed.
+ */
+typedef int (*xx_binaryheap_comparator) (bh_node_type a, bh_node_type b, void *arg);
+
+typedef void (*xx_binaryheap_update_index_fn) (bh_node_type node, int new_element_index);
+
+/*
+ * xx_binaryheap
+ *
+ *		bh_size			how many nodes are currently in "nodes"
+ *		bh_space		how many nodes can be stored in "nodes"
+ *		bh_has_heap_property	no unordered operations since last heap build
+ *		bh_compare		comparison function to define the heap property
+ *		bh_arg			user data for comparison function
+ *		bh_nodes		variable-length array of "space" nodes
+ */
+typedef struct xx_binaryheap
+{
+	int			bh_size;
+	int			bh_space;
+	bool		bh_has_heap_property;	/* debugging cross-check */
+	xx_binaryheap_comparator bh_compare;
+	xx_binaryheap_update_index_fn	bh_update_index;
+	void	   *bh_arg;
+	bh_node_type *bh_nodes;
+} xx_binaryheap;
+
+extern xx_binaryheap *xx_binaryheap_allocate(int num_nodes,
+											 xx_binaryheap_comparator compare,
+											 void *arg,
+											 xx_binaryheap_update_index_fn update_index);
+extern void xx_binaryheap_reset(xx_binaryheap *heap);
+extern void xx_binaryheap_free(xx_binaryheap *heap);
+extern void xx_binaryheap_add_unordered(xx_binaryheap *heap, bh_node_type d);
+extern void xx_binaryheap_build(xx_binaryheap *heap);
+extern void xx_binaryheap_add(xx_binaryheap *heap, bh_node_type d);
+extern bh_node_type xx_binaryheap_first(xx_binaryheap *heap);
+extern bh_node_type xx_binaryheap_remove_first(xx_binaryheap *heap);
+extern void xx_binaryheap_remove_node(xx_binaryheap *heap, int n);
+extern void xx_binaryheap_replace_first(xx_binaryheap *heap, bh_node_type d);
+extern void xx_binaryheap_update_up(xx_binaryheap *heap, int index);
+extern void xx_binaryheap_update_down(xx_binaryheap *heap, int index);
+
+#define xx_binaryheap_empty(h)			((h)->bh_size == 0)
+#define xx_binaryheap_size(h)			((h)->bh_size)
+#define xx_binaryheap_get_node(h, n)	((h)->bh_nodes[n])
+#define xx_binaryheap_indexed(h)		((h)->bh_update_index != NULL)
+
+#endif							/* XX_BINARYHEAP_H */
-- 
2.39.3

