1
0
mirror of https://github.com/postgres/postgres.git synced 2025-12-18 05:01:01 +03:00

bufmgr: Separate keys for private refcount infrastructure

This makes lookups faster, due to allowing auto-vectorized lookups. It is also
beneficial for an upcoming patch, independent of auto-vectorization, as the
upcoming patch wants to track more information for each pinned buffer, making
the existing loop, iterating over an array of PrivateRefCountEntry, more
expensive due to increasing its size.

Reviewed-by: Melanie Plageman <melanieplageman@gmail.com>
Discussion: https://postgr.es/m/fvfmkr5kk4nyex56ejgxj3uzi63isfxovp2biecb4bspbjrze7@az2pljabhnff
This commit is contained in:
Andres Freund
2025-12-14 13:09:43 -05:00
parent 5b275a6e15
commit edbaaea0a9
2 changed files with 97 additions and 45 deletions

View File

@@ -90,10 +90,32 @@
*/
#define BUF_DROP_FULL_SCAN_THRESHOLD (uint64) (NBuffers / 32)
/*
* This is separated out from PrivateRefCountEntry to allow for copying all
* the data members via struct assignment.
*/
typedef struct PrivateRefCountData
{
/*
* How many times has the buffer been pinned by this backend.
*/
int32 refcount;
} PrivateRefCountData;
typedef struct PrivateRefCountEntry
{
/*
* Note that this needs to be same as the entry's corresponding
* PrivateRefCountArrayKeys[i], if the entry is stored in the array. We
* store it in both places as this is used for the hashtable key and
* because it is more convenient (passing around a PrivateRefCountEntry
* suffices to identify the buffer) and faster (checking the keys array is
* faster when checking many entries, checking the entry is faster if just
* checking a single entry).
*/
Buffer buffer;
int32 refcount;
PrivateRefCountData data;
} PrivateRefCountEntry;
/* 64 bytes, about the size of a cache line on common systems */
@@ -194,7 +216,8 @@ static BufferDesc *PinCountWaitBuf = NULL;
*
* To avoid - as we used to - requiring an array with NBuffers entries to keep
* track of local buffers, we use a small sequentially searched array
* (PrivateRefCountArray) and an overflow hash table (PrivateRefCountHash) to
* (PrivateRefCountArrayKeys, with the corresponding data stored in
* PrivateRefCountArray) and an overflow hash table (PrivateRefCountHash) to
* keep track of backend local pins.
*
* Until no more than REFCOUNT_ARRAY_ENTRIES buffers are pinned at once, all
@@ -212,11 +235,12 @@ static BufferDesc *PinCountWaitBuf = NULL;
* memory allocations in NewPrivateRefCountEntry() which can be important
* because in some scenarios it's called with a spinlock held...
*/
static Buffer PrivateRefCountArrayKeys[REFCOUNT_ARRAY_ENTRIES];
static struct PrivateRefCountEntry PrivateRefCountArray[REFCOUNT_ARRAY_ENTRIES];
static HTAB *PrivateRefCountHash = NULL;
static int32 PrivateRefCountOverflowed = 0;
static uint32 PrivateRefCountClock = 0;
static PrivateRefCountEntry *ReservedRefCountEntry = NULL;
static int ReservedRefCountSlot = -1;
static uint32 MaxProportionalPins;
@@ -259,7 +283,7 @@ static void
ReservePrivateRefCountEntry(void)
{
/* Already reserved (or freed), nothing to do */
if (ReservedRefCountEntry != NULL)
if (ReservedRefCountSlot != -1)
return;
/*
@@ -271,17 +295,20 @@ ReservePrivateRefCountEntry(void)
for (i = 0; i < REFCOUNT_ARRAY_ENTRIES; i++)
{
PrivateRefCountEntry *res;
res = &PrivateRefCountArray[i];
if (res->buffer == InvalidBuffer)
if (PrivateRefCountArrayKeys[i] == InvalidBuffer)
{
ReservedRefCountEntry = res;
ReservedRefCountSlot = i;
/*
* We could return immediately, but iterating till the end of
* the array allows compiler-autovectorization.
*/
}
}
if (ReservedRefCountSlot != -1)
return;
}
}
}
/*
* No luck. All array entries are full. Move one array entry into the hash
@@ -292,27 +319,37 @@ ReservePrivateRefCountEntry(void)
* Move entry from the current clock position in the array into the
* hashtable. Use that slot.
*/
int victim_slot;
PrivateRefCountEntry *victim_entry;
PrivateRefCountEntry *hashent;
bool found;
/* select victim slot */
ReservedRefCountEntry =
&PrivateRefCountArray[PrivateRefCountClock++ % REFCOUNT_ARRAY_ENTRIES];
victim_slot = PrivateRefCountClock++ % REFCOUNT_ARRAY_ENTRIES;
victim_entry = &PrivateRefCountArray[victim_slot];
ReservedRefCountSlot = victim_slot;
/* Better be used, otherwise we shouldn't get here. */
Assert(ReservedRefCountEntry->buffer != InvalidBuffer);
Assert(PrivateRefCountArrayKeys[victim_slot] != InvalidBuffer);
Assert(PrivateRefCountArray[victim_slot].buffer != InvalidBuffer);
Assert(PrivateRefCountArrayKeys[victim_slot] == PrivateRefCountArray[victim_slot].buffer);
/* enter victim array entry into hashtable */
hashent = hash_search(PrivateRefCountHash,
&(ReservedRefCountEntry->buffer),
&PrivateRefCountArrayKeys[victim_slot],
HASH_ENTER,
&found);
Assert(!found);
hashent->refcount = ReservedRefCountEntry->refcount;
/* move data from the entry in the array to the hash entry */
hashent->data = victim_entry->data;
/* clear the now free array slot */
ReservedRefCountEntry->buffer = InvalidBuffer;
ReservedRefCountEntry->refcount = 0;
PrivateRefCountArrayKeys[victim_slot] = InvalidBuffer;
victim_entry->buffer = InvalidBuffer;
/* clear the whole data member, just for future proofing */
memset(&victim_entry->data, 0, sizeof(victim_entry->data));
victim_entry->data.refcount = 0;
PrivateRefCountOverflowed++;
}
@@ -327,15 +364,17 @@ NewPrivateRefCountEntry(Buffer buffer)
PrivateRefCountEntry *res;
/* only allowed to be called when a reservation has been made */
Assert(ReservedRefCountEntry != NULL);
Assert(ReservedRefCountSlot != -1);
/* use up the reserved entry */
res = ReservedRefCountEntry;
ReservedRefCountEntry = NULL;
res = &PrivateRefCountArray[ReservedRefCountSlot];
/* and fill it */
PrivateRefCountArrayKeys[ReservedRefCountSlot] = buffer;
res->buffer = buffer;
res->refcount = 0;
res->data.refcount = 0;
ReservedRefCountSlot = -1;
return res;
}
@@ -347,10 +386,11 @@ NewPrivateRefCountEntry(Buffer buffer)
* do_move is true, and the entry resides in the hashtable the entry is
* optimized for frequent access by moving it to the array.
*/
static PrivateRefCountEntry *
static inline PrivateRefCountEntry *
GetPrivateRefCountEntry(Buffer buffer, bool do_move)
{
PrivateRefCountEntry *res;
int match = -1;
int i;
Assert(BufferIsValid(buffer));
@@ -362,11 +402,15 @@ GetPrivateRefCountEntry(Buffer buffer, bool do_move)
*/
for (i = 0; i < REFCOUNT_ARRAY_ENTRIES; i++)
{
res = &PrivateRefCountArray[i];
if (res->buffer == buffer)
return res;
if (PrivateRefCountArrayKeys[i] == buffer)
{
match = i;
/* see ReservePrivateRefCountEntry() for why we don't return */
}
}
if (match != -1)
return &PrivateRefCountArray[match];
/*
* By here we know that the buffer, if already pinned, isn't residing in
@@ -397,14 +441,18 @@ GetPrivateRefCountEntry(Buffer buffer, bool do_move)
ReservePrivateRefCountEntry();
/* Use up the reserved slot */
Assert(ReservedRefCountEntry != NULL);
free = ReservedRefCountEntry;
ReservedRefCountEntry = NULL;
Assert(ReservedRefCountSlot != -1);
free = &PrivateRefCountArray[ReservedRefCountSlot];
Assert(PrivateRefCountArrayKeys[ReservedRefCountSlot] == free->buffer);
Assert(free->buffer == InvalidBuffer);
/* and fill it */
free->buffer = buffer;
free->refcount = res->refcount;
free->data = res->data;
PrivateRefCountArrayKeys[ReservedRefCountSlot] = buffer;
ReservedRefCountSlot = -1;
/* delete from hashtable */
hash_search(PrivateRefCountHash, &buffer, HASH_REMOVE, &found);
@@ -437,7 +485,7 @@ GetPrivateRefCount(Buffer buffer)
if (ref == NULL)
return 0;
return ref->refcount;
return ref->data.refcount;
}
/*
@@ -447,19 +495,21 @@ GetPrivateRefCount(Buffer buffer)
static void
ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref)
{
Assert(ref->refcount == 0);
Assert(ref->data.refcount == 0);
if (ref >= &PrivateRefCountArray[0] &&
ref < &PrivateRefCountArray[REFCOUNT_ARRAY_ENTRIES])
{
ref->buffer = InvalidBuffer;
PrivateRefCountArrayKeys[ref - PrivateRefCountArray] = InvalidBuffer;
/*
* Mark the just used entry as reserved - in many scenarios that
* allows us to avoid ever having to search the array/hash for free
* entries.
*/
ReservedRefCountEntry = ref;
ReservedRefCountSlot = ref - PrivateRefCountArray;
}
else
{
@@ -3073,7 +3123,7 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy,
PrivateRefCountEntry *ref;
Assert(!BufferIsLocal(b));
Assert(ReservedRefCountEntry != NULL);
Assert(ReservedRefCountSlot != -1);
ref = GetPrivateRefCountEntry(b, true);
@@ -3145,8 +3195,8 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy,
*/
result = (pg_atomic_read_u32(&buf->state) & BM_VALID) != 0;
Assert(ref->refcount > 0);
ref->refcount++;
Assert(ref->data.refcount > 0);
ref->data.refcount++;
ResourceOwnerRememberBuffer(CurrentResourceOwner, b);
}
@@ -3263,9 +3313,9 @@ UnpinBufferNoOwner(BufferDesc *buf)
/* not moving as we're likely deleting it soon anyway */
ref = GetPrivateRefCountEntry(b, false);
Assert(ref != NULL);
Assert(ref->refcount > 0);
ref->refcount--;
if (ref->refcount == 0)
Assert(ref->data.refcount > 0);
ref->data.refcount--;
if (ref->data.refcount == 0)
{
uint32 old_buf_state;
@@ -3305,7 +3355,7 @@ TrackNewBufferPin(Buffer buf)
PrivateRefCountEntry *ref;
ref = NewPrivateRefCountEntry(buf);
ref->refcount++;
ref->data.refcount++;
ResourceOwnerRememberBuffer(CurrentResourceOwner, buf);
@@ -4018,6 +4068,7 @@ InitBufferManagerAccess(void)
MaxProportionalPins = NBuffers / (MaxBackends + NUM_AUXILIARY_PROCS);
memset(&PrivateRefCountArray, 0, sizeof(PrivateRefCountArray));
memset(&PrivateRefCountArrayKeys, 0, sizeof(PrivateRefCountArrayKeys));
hash_ctl.keysize = sizeof(Buffer);
hash_ctl.entrysize = sizeof(PrivateRefCountEntry);
@@ -4066,11 +4117,11 @@ CheckForBufferLeaks(void)
/* check the array */
for (i = 0; i < REFCOUNT_ARRAY_ENTRIES; i++)
{
if (PrivateRefCountArrayKeys[i] != InvalidBuffer)
{
res = &PrivateRefCountArray[i];
if (res->buffer != InvalidBuffer)
{
s = DebugPrintBufferRefcount(res->buffer);
elog(WARNING, "buffer refcount leak: %s", s);
pfree(s);
@@ -5407,7 +5458,7 @@ IncrBufferRefCount(Buffer buffer)
ref = GetPrivateRefCountEntry(buffer, true);
Assert(ref != NULL);
ref->refcount++;
ref->data.refcount++;
}
ResourceOwnerRememberBuffer(CurrentResourceOwner, buffer);
}

View File

@@ -2333,6 +2333,7 @@ PrintfArgValue
PrintfTarget
PrinttupAttrInfo
PrivTarget
PrivateRefCountData
PrivateRefCountEntry
ProcArrayStruct
ProcLangInfo