From 85abc829474a4b0f40461af169dd0803e2e22c88 Mon Sep 17 00:00:00 2001
From: Alexandre Felipe <o.alexandre.felipe@gmail.com>
Date: Fri, 6 Mar 2026 17:46:38 +0000
Subject: [PATCH 5/5] REFCOUNT_ARRAY_ENTRIES=0

The simple hash performance is is fairly close to the direct array.
For few pins we are trading one small for loop for an index calculation
(buffer % N), this could be a (buffer & (N-1)) if we restrict the
simple array to use powers of 2 sizes.

For more than REFCOUNT_ARRAY_ENTRIES on distinct buffers pinned/unpinned in
a FIFO fashion, this is a strict improvement as every pin requires a hash
table operation.

---
 src/backend/storage/buffer/buf_refcount.c     |   60 +-
 src/include/storage/buf_refcount.h            |    3 +
 2 files changed

diff --git a/src/backend/storage/buffer/buf_refcount.c b/src/backend/storage/buffer/buf_refcount.c
index 29dfb720997..90cb42edbb5 100644
--- a/src/backend/storage/buffer/buf_refcount.c
+++ b/src/backend/storage/buffer/buf_refcount.c
@@ -88,17 +88,18 @@ struct PrivateRefCountIterator
 #define SH_DEFINE
 #include "lib/simplehash.h"
 
-/* Private refcount array and keys */
-#define REFCOUNT_ARRAY_ENTRIES 8
+
+/*
+ * Private refcount array and keys
+ * If set to 0, all the code handling the transfers between the array
+ * and the hash table is disabled at compilation time.
+*/
+#define REFCOUNT_ARRAY_ENTRIES 0
+
+#if REFCOUNT_ARRAY_ENTRIES > 0
 static Buffer PrivateRefCountArrayKeys[REFCOUNT_ARRAY_ENTRIES];
 static struct PrivateRefCountEntry PrivateRefCountArray[REFCOUNT_ARRAY_ENTRIES];
 
-/* Overflow hash table for when array is full */
-static refcount_hash *PrivateRefCountHash = NULL;
-
-/* Count of entries that have overflowed into the hash table */
-static int32 PrivateRefCountOverflowed = 0;
-
 /* Clock hand for selecting victim when array is full */
 static uint32 PrivateRefCountClock = 0;
 
@@ -107,14 +108,23 @@ static int	ReservedRefCountSlot = -1;
 
 /* Cache for last accessed entry */
 static int	PrivateRefCountEntryLast = -1;
+#endif
 
 /* Advisory limit on the number of pins each backend should hold */
 static uint32 MaxProportionalPins = 0;
 
+/* Hash table (overflow when array used, primary when hash-only) */
+static refcount_hash *PrivateRefCountHash = NULL;
+
+/* Count of entries in the hash table */
+static int32 PrivateRefCountOverflowed = 0;
+
+#if REFCOUNT_ARRAY_ENTRIES > 0
 /* Forward declarations */
 static void ReservePrivateRefCountEntry(void);
 static PrivateRefCountEntry *NewPrivateRefCountEntry(Buffer buffer);
 static pg_noinline PrivateRefCountEntry *GetPrivateRefCountEntrySlow(Buffer buffer);
+#endif
 
 /*
  * Initialize private refcount tracking for this backend.
@@ -130,12 +140,15 @@ InitPrivateRefCount(void)
 	 * GetAdditionalPinLimit() can be used to check the remaining balance.
 	*/
 	MaxProportionalPins = NBuffers / (MaxBackends + NUM_AUXILIARY_PROCS);
+#if REFCOUNT_ARRAY_ENTRIES > 0
 	memset(&PrivateRefCountArray, 0, sizeof(PrivateRefCountArray));
 	memset(&PrivateRefCountArrayKeys, 0, sizeof(PrivateRefCountArrayKeys));
+#endif
 
 	PrivateRefCountHash = refcount_create(CurrentMemoryContext, 64, NULL);
 }
 
+#if REFCOUNT_ARRAY_ENTRIES > 0
 /*
  * Ensure that the PrivateRefCountArray has sufficient space to store one more
  * entry.
@@ -233,7 +246,9 @@ NewPrivateRefCountEntry(Buffer buffer)
 
 	return res;
 }
+#endif /* REFCOUNT_ARRAY_ENTRIES > 0 */
 
+#if REFCOUNT_ARRAY_ENTRIES > 0
 /*
  * Slow-path for GetSharedBufferEntry().
  */
@@ -270,6 +285,7 @@ GetPrivateRefCountEntrySlow(Buffer buffer)
 	res = refcount_lookup(PrivateRefCountHash, buffer);
 	return res;
 }
+#endif /* REFCOUNT_ARRAY_ENTRIES > 0 */
 
 /*
  * Return the PrivateRefCount entry for the passed buffer.
@@ -281,6 +297,7 @@ GetSharedBufferEntry(Buffer buffer)
 	Assert(BufferIsValid(buffer));
 	Assert(!BufferIsLocal(buffer));
 
+#if REFCOUNT_ARRAY_ENTRIES > 0
 	/* Fast path: check one-entry cache */
 	if (likely(PrivateRefCountEntryLast != -1) &&
 		likely(PrivateRefCountArray[PrivateRefCountEntryLast].buffer == buffer))
@@ -289,6 +306,10 @@ GetSharedBufferEntry(Buffer buffer)
 	}
 
 	return GetPrivateRefCountEntrySlow(buffer);
+#else
+	/* Hash-only mode: direct lookup */
+	return refcount_lookup(PrivateRefCountHash, buffer);
+#endif
 }
 
 /*
@@ -308,10 +329,20 @@ SharedBufferRef(Buffer buffer)
 
 	if (ref == NULL)
 	{
-		/* New pin - create entry */
+#if REFCOUNT_ARRAY_ENTRIES > 0
+		/* New pin - create entry in array */
 		ReservePrivateRefCountEntry();
 		ref = NewPrivateRefCountEntry(buffer);
 		ref->data = ONE_PRIVATE_REFERENCE;
+#else
+		/* Hash-only mode: insert directly */
+		bool		found;
+
+		ref = refcount_insert(PrivateRefCountHash, buffer, &found);
+		Assert(!found);
+		ref->data = ONE_PRIVATE_REFERENCE;
+		PrivateRefCountOverflowed++;
+#endif
 	}
 	else
 	{
@@ -352,6 +383,7 @@ SharedBufferUnref(PrivateRefCountEntry *ref)
 		/* No more references - clean up the entry */
 		Assert(SharedBufferGetLockMode(ref) == BUFFER_LOCK_UNLOCK);
 
+#if REFCOUNT_ARRAY_ENTRIES > 0
 		if (ref >= &PrivateRefCountArray[0] &&
 			ref < &PrivateRefCountArray[REFCOUNT_ARRAY_ENTRIES])
 		{
@@ -360,6 +392,7 @@ SharedBufferUnref(PrivateRefCountEntry *ref)
 			ReservedRefCountSlot = ref - PrivateRefCountArray;
 		}
 		else
+#endif
 		{
 			/* could make slightly more efficient by using the pointer */
 			refcount_delete(PrivateRefCountHash, ref->buffer);
@@ -409,11 +442,11 @@ CheckPrivateRefCountLeaks(void)
 #ifdef USE_ASSERT_CHECKING
 	int			RefCountErrors = 0;
 	PrivateRefCountEntry *res;
-	int			i;
 	char	   *s;
 
+#if REFCOUNT_ARRAY_ENTRIES > 0
 	/* check the array */
-	for (i = 0; i < REFCOUNT_ARRAY_ENTRIES; i++)
+	for (int i = 0; i < REFCOUNT_ARRAY_ENTRIES; i++)
 	{
 		if (PrivateRefCountArrayKeys[i] != InvalidBuffer)
 		{
@@ -426,8 +459,9 @@ CheckPrivateRefCountLeaks(void)
 			RefCountErrors++;
 		}
 	}
+#endif
 
-	/* if necessary search the hash */
+	/* search the hash */
 	if (PrivateRefCountOverflowed)
 	{
 		refcount_iterator iter;
@@ -467,6 +501,7 @@ InitPrivateRefCountIterator(void)
 PrivateRefCountEntry *
 GetNextPrivateRefCountEntry(PrivateRefCountIterator *iter)
 {
+#if REFCOUNT_ARRAY_ENTRIES > 0
 	/* First iterate through the array */
 	while (!iter->in_hash && iter->array_index < REFCOUNT_ARRAY_ENTRIES)
 	{
@@ -475,8 +510,9 @@ GetNextPrivateRefCountEntry(PrivateRefCountIterator *iter)
 		if (PrivateRefCountArrayKeys[idx] != InvalidBuffer)
 			return &PrivateRefCountArray[idx];
 	}
+#endif
 
-	/* Then iterate through the hash if there are overflowed entries */
+	/* Then iterate through the hash if there are entries */
 	if (!iter->in_hash)
 	{
 		iter->in_hash = true;
-- 
2.53.0

