From 7fa7181655627d7ed5eafb33341621d1965c5312 Mon Sep 17 00:00:00 2001 From: Zhiguo Zhou Date: Thu, 29 May 2025 16:55:42 +0800 Subject: [PATCH] Optimize shared LWLock acquisition for high-core-count systems This patch introduces optimizations to reduce lock acquisition overhead in LWLock by merging the read and update operations for the LW_SHARED lock's state. This eliminates the need for separate atomic instructions, which is critical for improving performance on high-core-count systems. Key changes: - Extended LW_SHARED_MASK by 1 bit and shifted LW_VAL_EXCLUSIVE by 1 bit to ensure compatibility with the upper bound of MAX_BACKENDS * 2. - Added a `willwait` parameter to `LWLockAttemptLock` to disable the optimization when the caller is unwilling to wait, avoiding conflicts between the reference count and the LW_VAL_EXCLUSIVE flag. - Updated `LWLockReleaseInternal` to use `pg_atomic_fetch_and_u32` for clearing lock state flags atomically. - Adjusted related functions (`LWLockAcquire`, `LWLockConditionalAcquire`, `LWLockAcquireOrWait`) to pass the `willwait` parameter appropriately. These changes improve scalability and reduce contention in workloads with frequent LWLock operations on servers with many cores. --- src/backend/storage/lmgr/lwlock.c | 73 ++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c index 46f44bc4511..4c29016ce35 100644 --- a/src/backend/storage/lmgr/lwlock.c +++ b/src/backend/storage/lmgr/lwlock.c @@ -97,20 +97,41 @@ #define LW_FLAG_BITS 3 #define LW_FLAG_MASK (((1<state); + uint32 excl = (state & LW_VAL_EXCLUSIVE) != 0; + uint32 shared = excl ? 0 : state & LW_SHARED_MASK; ereport(LOG, (errhidestmt(true), @@ -284,8 +307,8 @@ PRINT_LWDEBUG(const char *where, LWLock *lock, LWLockMode mode) errmsg_internal("%d: %s(%s %p): excl %u shared %u haswaiters %u waiters %u rOK %d", MyProcPid, where, T_NAME(lock), lock, - (state & LW_VAL_EXCLUSIVE) != 0, - state & LW_SHARED_MASK, + excl, + shared, (state & LW_FLAG_HAS_WAITERS) != 0, pg_atomic_read_u32(&lock->nwaiters), (state & LW_FLAG_RELEASE_OK) != 0))); @@ -790,15 +813,30 @@ GetLWLockIdentifier(uint32 classId, uint16 eventId) * This function will not block waiting for a lock to become free - that's the * caller's job. * + * willwait: true if the caller is willing to wait for the lock to become free + * false if the caller is not willing to wait. + * * Returns true if the lock isn't free and we need to wait. */ static bool -LWLockAttemptLock(LWLock *lock, LWLockMode mode) +LWLockAttemptLock(LWLock *lock, LWLockMode mode, bool willwait) { uint32 old_state; Assert(mode == LW_EXCLUSIVE || mode == LW_SHARED); + /* + * To avoid conflicts between the reference count and the LW_VAL_EXCLUSIVE + * flag, this optimization is disabled when willwait is false. See detailed + * comments in this file where LW_SHARED_MASK is defined for more explaination. + */ + if (willwait && mode == LW_SHARED) + { + old_state = pg_atomic_fetch_add_u32(&lock->state, LW_VAL_SHARED); + Assert((old_state & LW_LOCK_MASK) != LW_LOCK_MASK); + return (old_state & LW_VAL_EXCLUSIVE) != 0; + } + /* * Read once outside the loop, later iterations will get the newer value * via compare & exchange. @@ -1242,7 +1280,7 @@ LWLockAcquire(LWLock *lock, LWLockMode mode) * Try to grab the lock the first time, we're not in the waitqueue * yet/anymore. */ - mustwait = LWLockAttemptLock(lock, mode); + mustwait = LWLockAttemptLock(lock, mode, true); if (!mustwait) { @@ -1265,7 +1303,7 @@ LWLockAcquire(LWLock *lock, LWLockMode mode) LWLockQueueSelf(lock, mode); /* we're now guaranteed to be woken up if necessary */ - mustwait = LWLockAttemptLock(lock, mode); + mustwait = LWLockAttemptLock(lock, mode, true); /* ok, grabbed the lock the second time round, need to undo queueing */ if (!mustwait) @@ -1368,7 +1406,7 @@ LWLockConditionalAcquire(LWLock *lock, LWLockMode mode) HOLD_INTERRUPTS(); /* Check for the lock */ - mustwait = LWLockAttemptLock(lock, mode); + mustwait = LWLockAttemptLock(lock, mode, false); if (mustwait) { @@ -1435,13 +1473,13 @@ LWLockAcquireOrWait(LWLock *lock, LWLockMode mode) * NB: We're using nearly the same twice-in-a-row lock acquisition * protocol as LWLockAcquire(). Check its comments for details. */ - mustwait = LWLockAttemptLock(lock, mode); + mustwait = LWLockAttemptLock(lock, mode, true); if (mustwait) { LWLockQueueSelf(lock, LW_WAIT_UNTIL_FREE); - mustwait = LWLockAttemptLock(lock, mode); + mustwait = LWLockAttemptLock(lock, mode, true); if (mustwait) { @@ -1843,7 +1881,10 @@ LWLockReleaseInternal(LWLock *lock, LWLockMode mode) * others, even if we still have to wakeup other waiters. */ if (mode == LW_EXCLUSIVE) - oldstate = pg_atomic_sub_fetch_u32(&lock->state, LW_VAL_EXCLUSIVE); + { + oldstate = pg_atomic_fetch_and_u32(&lock->state, ~LW_LOCK_MASK); + oldstate &= ~LW_LOCK_MASK; + } else oldstate = pg_atomic_sub_fetch_u32(&lock->state, LW_VAL_SHARED); -- 2.43.0