From 85abc829474a4b0f40461af169dd0803e2e22c88 Mon Sep 17 00:00:00 2001 From: Alexandre Felipe 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