v2-0002-Provide-SetLatches-for-batched-deferred-latches.patch
application/octet-stream
Filename: v2-0002-Provide-SetLatches-for-batched-deferred-latches.patch
Type: application/octet-stream
Part: 1
Message:
Re: Latches vs lwlock contention
Patch
Format: format-patch
Series: patch v2-0002
Subject: Provide SetLatches() for batched deferred latches.
| File | + | − |
|---|---|---|
| src/backend/storage/ipc/latch.c | 149 | 74 |
| src/include/storage/latch.h | 13 | 0 |
| src/tools/pgindent/typedefs.list | 1 | 0 |
From e30aab2276b05212dbfe23d2a28636036610645d Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Wed, 26 Oct 2022 15:51:45 +1300
Subject: [PATCH v2 2/9] Provide SetLatches() for batched deferred latches.
If we have a way to buffer a set of wakeup targets and process them at a
later time, we can:
* move SetLatch() system calls out from under LWLocks, so that locks can
be released faster; this is especially interesting in cases where the
target backends will immediately try to acquire the same lock, or
generally when the lock is heavily contended
* possibly gain some micro-opimization from issuing only two memory
barriers for the whole batch of latches, not two for each latch to be
set
* prepare for future IPC mechanisms which might allow us to wake
multiple backends in a single system call
Individual users of this facility will follow in separate patches.
Discussion: https://postgr.es/m/CA%2BhUKGKmO7ze0Z6WXKdrLxmvYa%3DzVGGXOO30MMktufofVwEm1A%40mail.gmail.com
---
src/backend/storage/ipc/latch.c | 223 +++++++++++++++++++++----------
src/include/storage/latch.h | 13 ++
src/tools/pgindent/typedefs.list | 1 +
3 files changed, 163 insertions(+), 74 deletions(-)
diff --git a/src/backend/storage/ipc/latch.c b/src/backend/storage/ipc/latch.c
index eb3a569aae..199ccc1e65 100644
--- a/src/backend/storage/ipc/latch.c
+++ b/src/backend/storage/ipc/latch.c
@@ -575,6 +575,94 @@ WaitLatchOrSocket(Latch *latch, int wakeEvents, pgsocket sock,
return ret;
}
+/*
+ * Set multiple latches at the same time.
+ * Note: modifies input array.
+ */
+static void
+SetLatchV(Latch **latches, int nlatches)
+{
+ /* Flush any other changes out to main memory just once. */
+ pg_memory_barrier();
+
+ /* Keep only latches that are not already set, and set them. */
+ for (int i = 0; i < nlatches; ++i)
+ {
+ Latch *latch = latches[i];
+
+ if (!latch->is_set)
+ latch->is_set = true;
+ else
+ latches[i] = NULL;
+ }
+
+ pg_memory_barrier();
+
+ /* Wake the remaining latches that might be sleeping. */
+#ifndef WIN32
+ for (int i = 0; i < nlatches; ++i)
+ {
+ Latch *latch = latches[i];
+ pid_t owner_pid;
+
+ if (!latch || !latch->maybe_sleeping)
+ continue;
+
+ /*
+ * See if anyone's waiting for the latch. It can be the current
+ * process if we're in a signal handler. We use the self-pipe or
+ * SIGURG to ourselves to wake up WaitEventSetWaitBlock() without
+ * races in that case. If it's another process, send a signal.
+ *
+ * Fetch owner_pid only once, in case the latch is concurrently
+ * getting owned or disowned. XXX: This assumes that pid_t is atomic,
+ * which isn't guaranteed to be true! In practice, the effective range
+ * of pid_t fits in a 32 bit integer, and so should be atomic. In the
+ * worst case, we might end up signaling the wrong process. Even then,
+ * you're very unlucky if a process with that bogus pid exists and
+ * belongs to Postgres; and PG database processes should handle excess
+ * SIGURG interrupts without a problem anyhow.
+ *
+ * Another sort of race condition that's possible here is for a new
+ * process to own the latch immediately after we look, so we don't
+ * signal it. This is okay so long as all callers of
+ * ResetLatch/WaitLatch follow the standard coding convention of
+ * waiting at the bottom of their loops, not the top, so that they'll
+ * correctly process latch-setting events that happen before they
+ * enter the loop.
+ */
+ owner_pid = latch->owner_pid;
+
+ if (owner_pid == MyProcPid)
+ {
+ if (waiting)
+ {
+#if defined(WAIT_USE_SELF_PIPE)
+ sendSelfPipeByte();
+#else
+ kill(MyProcPid, SIGURG);
+#endif
+ }
+ }
+ else
+ kill(owner_pid, SIGURG);
+ }
+#else
+ for (int i = 0; i < nlatches; ++i)
+ {
+ Latch *latch = latches[i];
+
+ if (latch && latch->maybe_sleeping)
+ {
+ HANDLE event = latch->event;
+
+ if (event)
+ SetEvent(event);
+ }
+ }
+#endif
+}
+
/*
* Sets a latch and wakes up anyone waiting on it.
*
@@ -590,89 +678,76 @@ WaitLatchOrSocket(Latch *latch, int wakeEvents, pgsocket sock,
void
SetLatch(Latch *latch)
{
-#ifndef WIN32
- pid_t owner_pid;
-#else
- HANDLE handle;
-#endif
-
- /*
- * The memory barrier has to be placed here to ensure that any flag
- * variables possibly changed by this process have been flushed to main
- * memory, before we check/set is_set.
- */
- pg_memory_barrier();
-
- /* Quick exit if already set */
- if (latch->is_set)
- return;
-
- latch->is_set = true;
-
- pg_memory_barrier();
- if (!latch->maybe_sleeping)
- return;
+ SetLatchV(&latch, 1);
+}
-#ifndef WIN32
+/*
+ * Add a latch to a group, to be set later.
+ */
+void
+AddLatch(LatchGroup *group, Latch *latch)
+{
+ /* First time. Set up the in-place buffer. */
+ if (!group->latches)
+ {
+ group->latches = group->in_place_buffer;
+ group->capacity = lengthof(group->in_place_buffer);
+ Assert(group->size == 0);
+ }
- /*
- * See if anyone's waiting for the latch. It can be the current process if
- * we're in a signal handler. We use the self-pipe or SIGURG to ourselves
- * to wake up WaitEventSetWaitBlock() without races in that case. If it's
- * another process, send a signal.
- *
- * Fetch owner_pid only once, in case the latch is concurrently getting
- * owned or disowned. XXX: This assumes that pid_t is atomic, which isn't
- * guaranteed to be true! In practice, the effective range of pid_t fits
- * in a 32 bit integer, and so should be atomic. In the worst case, we
- * might end up signaling the wrong process. Even then, you're very
- * unlucky if a process with that bogus pid exists and belongs to
- * Postgres; and PG database processes should handle excess SIGUSR1
- * interrupts without a problem anyhow.
- *
- * Another sort of race condition that's possible here is for a new
- * process to own the latch immediately after we look, so we don't signal
- * it. This is okay so long as all callers of ResetLatch/WaitLatch follow
- * the standard coding convention of waiting at the bottom of their loops,
- * not the top, so that they'll correctly process latch-setting events
- * that happen before they enter the loop.
- */
- owner_pid = latch->owner_pid;
- if (owner_pid == 0)
- return;
- else if (owner_pid == MyProcPid)
+ /* Are we full? */
+ if (group->size == group->capacity)
{
-#if defined(WAIT_USE_SELF_PIPE)
- if (waiting)
- sendSelfPipeByte();
-#else
- if (waiting)
- kill(MyProcPid, SIGURG);
-#endif
+ Latch **new_latches;
+ int new_capacity;
+
+ /* Try to allocate more space. */
+ new_capacity = group->capacity * 2;
+ new_latches = palloc_extended(sizeof(latch) * new_capacity,
+ MCXT_ALLOC_NO_OOM);
+ if (!new_latches)
+ {
+ /*
+ * Allocation failed. This is very unlikely to happen, but it
+ * might be useful to be able to use this function in critical
+ * sections, so we handle allocation failure by flushing instead
+ * of throwing.
+ */
+ SetLatches(group);
+ }
+ else
+ {
+ memcpy(new_latches, group->latches, sizeof(latch) * group->size);
+ if (group->latches != group->in_place_buffer)
+ pfree(group->latches);
+ group->latches = new_latches;
+ group->capacity = new_capacity;
+ }
}
- else
- kill(owner_pid, SIGURG);
-#else
+ Assert(group->size < group->capacity);
+ group->latches[group->size++] = latch;
+}
- /*
- * See if anyone's waiting for the latch. It can be the current process if
- * we're in a signal handler.
- *
- * Use a local variable here just in case somebody changes the event field
- * concurrently (which really should not happen).
- */
- handle = latch->event;
- if (handle)
+/*
+ * Set all the latches accumulated in 'group'.
+ */
+void
+SetLatches(LatchGroup *group)
+{
+ if (group->size > 0)
{
- SetEvent(handle);
+ SetLatchV(group->latches, group->size);
+ group->size = 0;
- /*
- * Note that we silently ignore any errors. We might be in a signal
- * handler or other critical path where it's not safe to call elog().
- */
+ /* If we allocated a large buffer, it's time to free it. */
+ if (group->latches != group->in_place_buffer)
+ {
+ pfree(group->latches);
+ group->latches = group->in_place_buffer;
+ group->capacity = lengthof(group->in_place_buffer);
+ }
}
-#endif
}
/*
diff --git a/src/include/storage/latch.h b/src/include/storage/latch.h
index 68ab740f16..09c2b338c6 100644
--- a/src/include/storage/latch.h
+++ b/src/include/storage/latch.h
@@ -118,6 +118,17 @@ typedef struct Latch
#endif
} Latch;
+/*
+ * A buffer for setting multiple latches at a time.
+ */
+typedef struct LatchGroup
+{
+ int size;
+ int capacity;
+ Latch **latches;
+ Latch *in_place_buffer[64];
+} LatchGroup;
+
/*
* Bitmasks for events that may wake-up WaitLatch(), WaitLatchOrSocket(), or
* WaitEventSetWait().
@@ -163,6 +174,8 @@ extern void InitSharedLatch(Latch *latch);
extern void OwnLatch(Latch *latch);
extern void DisownLatch(Latch *latch);
extern void SetLatch(Latch *latch);
+extern void AddLatch(LatchGroup *group, Latch *latch);
+extern void SetLatches(LatchGroup *group);
extern void ResetLatch(Latch *latch);
extern void ShutdownLatchSupport(void);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 2f02cc8f42..c86e49c758 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1389,6 +1389,7 @@ LagTracker
LargeObjectDesc
LastAttnumInfo
Latch
+LatchGroup
LerpFunc
LexDescr
LexemeEntry
--
2.37.0 (Apple Git-136)