From 8ea04a74b34f0f86d9e49a8001fb6e452a3e39cb Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Tue, 18 Nov 2014 19:48:35 +0200
Subject: [PATCH 2/2] Bucket sort

---
 src/backend/storage/page/bufpage.c | 96 +++++++++++++++++++++++++++++++++++---
 1 file changed, 89 insertions(+), 7 deletions(-)

diff --git a/src/backend/storage/page/bufpage.c b/src/backend/storage/page/bufpage.c
index ac6e537..c30dd55 100644
--- a/src/backend/storage/page/bufpage.c
+++ b/src/backend/storage/page/bufpage.c
@@ -410,12 +410,94 @@ typedef struct itemIdSortData
 } itemIdSortData;
 typedef itemIdSortData *itemIdSort;
 
-static int
-itemoffcompare(const void *itemidp1, const void *itemidp2)
+/*
+ * Sort an array of itemIdSort's on itemoff, descending.
+ *
+ * This performs a simple insertion sort. The arrays are expected to be
+ * very small, so this is faster than a qsort(), thanks to all the function
+ * call etc. overhead in the generic qsort(), .
+ */
+static inline void
+sort_itemIds_small(itemIdSort itemidbase, int nitems)
+{
+	int			i,
+				j;
+
+	for (i = 1; i < nitems; i++)
+	{
+		itemIdSortData tmp = itemidbase[i];
+
+		for (j = i; j > 0 && itemidbase[j - 1].itemoff < tmp.itemoff; j--)
+			itemidbase[j] = itemidbase[j - 1];
+		itemidbase[j] = tmp;
+	}
+}
+
+/*
+ * Sort an array of itemIdSort's on itemoff, descending.
+ *
+ * The caller must pass a working array of same size as the source array.
+ * This function returns a pointer to the sorted array, which can be the
+ * original array, or the working array.
+ */
+static itemIdSort
+sort_itemIds(itemIdSort itemidbase, int nitems, itemIdSort work)
 {
-	/* Sort in decreasing itemoff order */
-	return ((itemIdSort) itemidp2)->itemoff -
-		((itemIdSort) itemidp1)->itemoff;
+#define N_SORT_BUCKETS			32
+#define SORT_BUCKET_SIZE		(BLCKSZ / 32)
+	uint16		counts[N_SORT_BUCKETS];
+	uint16		positions[N_SORT_BUCKETS];
+	int			i,
+				j;
+	uint16		total;
+
+	/*
+	 * For a small number of entries, just do an insertion sort. Otherwise
+	 * perform a bucket sort.
+	 */
+	if (nitems < 20)
+	{
+		sort_itemIds_small(itemidbase, nitems);
+
+		return itemidbase;
+	}
+
+	/* First count the number of entries that will fall into each bucket */
+	memset(counts, 0, sizeof(counts));
+	for (i = 0; i < nitems; i++)
+	{
+		j = itemidbase[i].itemoff / SORT_BUCKET_SIZE;
+		counts[j]++;
+	}
+
+	/*
+	 * Calculate the bucket boundaries in the destination array.
+	 * After this, positions[i] is the last index + 1 belonging to bucket i.
+	 */
+	total = nitems;
+	for (i = 0; i < N_SORT_BUCKETS; i++)
+	{
+		positions[i] = total;
+		total -= counts[i];
+	}
+
+	/*
+	 * Copy each item to the correct bucket. As a side effect, the positions
+	 * array is modified so that positions[i] becomes the first index belonging
+	 * to bucket i.
+	 */
+	for (i = 0; i < nitems; i++)
+	{
+		j = itemidbase[i].itemoff / SORT_BUCKET_SIZE;
+		positions[j]--;
+		work[positions[j]] = itemidbase[i];
+	}
+
+	/* Finally, sort each bucket, using insertion sort. */
+	for (i = 0; i < N_SORT_BUCKETS; i++)
+		sort_itemIds_small(&work[positions[i]], counts[i]);
+
+	return work;
 }
 
 /*
@@ -428,10 +510,10 @@ compactify_tuples(itemIdSort itemidbase, int nitems, Page page)
 	PageHeader	phdr = (PageHeader) page;
 	Offset		upper;
 	int			i;
+	itemIdSortData work[Max(MaxIndexTuplesPerPage, MaxHeapTuplesPerPage)];
 
 	/* sort itemIdSortData array into decreasing itemoff order */
-	qsort((char *) itemidbase, nitems, sizeof(itemIdSortData),
-		  itemoffcompare);
+	itemidbase = sort_itemIds(itemidbase, nitems, work);
 
 	upper = phdr->pd_special;
 	for (i = 0; i < nitems; i++)
-- 
2.1.3

