From 67584ff4eb485caf15d517a91c6bd6500b3efadf Mon Sep 17 00:00:00 2001 From: Greg Burd Date: Fri, 19 Jun 2026 21:50:31 -0400 Subject: [PATCH] perf(lock): take the locker mutex shared on the lock-get hot path Every DB_ENV->lock_get / lock_put resolves its locker through __lock_getlocker_int under the region-global locker mutex (mtx_lockers). On the lock-get path the lookup is create=0 -- a read-only walk of the locker hash bucket -- yet it was held *exclusive*, serializing every lock acquisition across all cores even when objects are fully partitioned and there is no lock conflict. Make mtx_lockers a DB_MUTEX_SHARED latch and take it in shared mode for the read-only locker lookup on the hot path (__lock_get_api). Locker create, free, the deadlock detector's locker-list walk, failchk, and stat continue to hold it exclusive, so they never run concurrently with a reader. Measured with lab/bench/lock_bench (distinct mode, no lock conflict, on a 24-thread box): master plateaus and then declines past 8 threads (~3.0M ops/s peak, 2.6M at 24t); the shared latch scales to 7.0M at 24t -- 2.1x at 8 threads, 2.7x at 24. It captures roughly half the upper bound of removing the mutex entirely; the remainder is the shared latch's own reference-count cache line, which would require partitioning the locker hash to recover (left for later -- this is the low-risk 80/20). A small single-thread regression (~8%) reflects the shared latch's slightly higher uncontended cost and is dwarfed by the multi-core gain. Verified: TCL lock001/002/003 (incl. multi-process), txn001/002, test001, ssi001/002 pass; concurrent shared read-lock acquisition (lock_bench shared) runs clean. --- src/dbinc/lock.h | 2 ++ src/lock/lock.c | 2 +- src/lock/lock_region.c | 10 +++++++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/dbinc/lock.h b/src/dbinc/lock.h index d6133e800..1090779ef 100644 --- a/src/dbinc/lock.h +++ b/src/dbinc/lock.h @@ -329,6 +329,8 @@ struct __db_lock { /* SHARED */ MUTEX_UNLOCK(env, (region)->mtx_dd) #define LOCK_LOCKERS(env, region) \ MUTEX_LOCK(env, (region)->mtx_lockers) +#define RDLOCK_LOCKERS(env, region) \ + MUTEX_READLOCK(env, (region)->mtx_lockers) #define UNLOCK_LOCKERS(env, region) \ MUTEX_UNLOCK(env, (region)->mtx_lockers) diff --git a/src/lock/lock.c b/src/lock/lock.c index 73400d92d..7bd16e870 100644 --- a/src/lock/lock.c +++ b/src/lock/lock.c @@ -596,7 +596,7 @@ __lock_get_api(env, locker, flags, obj, lock_mode, lock) region = env->lk_handle->reginfo.primary; - LOCK_LOCKERS(env, region); + RDLOCK_LOCKERS(env, region); ret = __lock_getlocker_int(env->lk_handle, locker, 0, &sh_locker); UNLOCK_LOCKERS(env, region); LOCK_SYSTEM_LOCK(env->lk_handle, region); diff --git a/src/lock/lock_region.c b/src/lock/lock_region.c index bd5a7a170..29695cdfb 100644 --- a/src/lock/lock_region.c +++ b/src/lock/lock_region.c @@ -253,8 +253,16 @@ __lock_region_init(env, lt) env, MTX_LOCK_REGION, 0, ®ion->mtx_dd)) != 0) return (ret); + /* + * The locker mutex is a SHARED latch: the hot lock-get path looks up + * an existing locker (a read-only hash walk) and takes it shared, so + * many cores can resolve their locker concurrently; locker create, + * free, the deadlock detector's locker-list walk, failchk, and stat + * take it exclusive. This removes the per-operation global + * serialization on lock_get/lock_put. + */ if ((ret = __mutex_alloc( - env, MTX_LOCK_REGION, 0, ®ion->mtx_lockers)) != 0) + env, MTX_LOCK_REGION, DB_MUTEX_SHARED, ®ion->mtx_lockers)) != 0) return (ret); /* Allocate room for the locker hash table and initialize it. */