Skip to content

Commit a72ee09

Browse files
committed
Add infrastructure for making spins_per_delay variable depending on
whether we seem to be running in a uniprocessor or multiprocessor. The adjustment rules could probably still use further tweaking, but I'm convinced this should be a win overall.
1 parent 9907b97 commit a72ee09

File tree

4 files changed

+145
-24
lines changed

4 files changed

+145
-24
lines changed

src/backend/storage/lmgr/proc.c

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/backend/storage/lmgr/proc.c,v 1.164 2005/09/19 17:21:47 momjian Exp $
11+
* $PostgreSQL: pgsql/src/backend/storage/lmgr/proc.c,v 1.165 2005/10/11 20:41:32 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -171,6 +171,8 @@ InitProcGlobal(void)
171171

172172
ProcGlobal->freeProcs = INVALID_OFFSET;
173173

174+
ProcGlobal->spins_per_delay = DEFAULT_SPINS_PER_DELAY;
175+
174176
/*
175177
* Pre-create the PGPROC structures and create a semaphore for
176178
* each.
@@ -225,9 +227,14 @@ InitProcess(void)
225227
/*
226228
* Try to get a proc struct from the free list. If this fails, we
227229
* must be out of PGPROC structures (not to mention semaphores).
230+
*
231+
* While we are holding the ProcStructLock, also copy the current
232+
* shared estimate of spins_per_delay to local storage.
228233
*/
229234
SpinLockAcquire(ProcStructLock);
230235

236+
set_spins_per_delay(procglobal->spins_per_delay);
237+
231238
myOffset = procglobal->freeProcs;
232239

233240
if (myOffset != INVALID_OFFSET)
@@ -319,21 +326,38 @@ InitDummyProcess(int proctype)
319326

320327
Assert(proctype >= 0 && proctype < NUM_DUMMY_PROCS);
321328

329+
/*
330+
* Just for paranoia's sake, we use the ProcStructLock to protect
331+
* assignment and releasing of DummyProcs entries.
332+
*
333+
* While we are holding the ProcStructLock, also copy the current
334+
* shared estimate of spins_per_delay to local storage.
335+
*/
336+
SpinLockAcquire(ProcStructLock);
337+
338+
set_spins_per_delay(ProcGlobal->spins_per_delay);
339+
322340
dummyproc = &DummyProcs[proctype];
323341

324342
/*
325343
* dummyproc should not presently be in use by anyone else
326344
*/
327345
if (dummyproc->pid != 0)
346+
{
347+
SpinLockRelease(ProcStructLock);
328348
elog(FATAL, "DummyProc[%d] is in use by PID %d",
329349
proctype, dummyproc->pid);
350+
}
330351
MyProc = dummyproc;
331352

353+
MyProc->pid = MyProcPid; /* marks dummy proc as in use by me */
354+
355+
SpinLockRelease(ProcStructLock);
356+
332357
/*
333358
* Initialize all fields of MyProc, except MyProc->sem which was set
334359
* up by InitProcGlobal.
335360
*/
336-
MyProc->pid = MyProcPid; /* marks dummy proc as in use by me */
337361
SHMQueueElemInit(&(MyProc->links));
338362
MyProc->waitStatus = STATUS_OK;
339363
MyProc->xid = InvalidTransactionId;
@@ -510,6 +534,9 @@ ProcKill(int code, Datum arg)
510534
/* PGPROC struct isn't mine anymore */
511535
MyProc = NULL;
512536

537+
/* Update shared estimate of spins_per_delay */
538+
procglobal->spins_per_delay = update_spins_per_delay(procglobal->spins_per_delay);
539+
513540
SpinLockRelease(ProcStructLock);
514541
}
515542

@@ -533,11 +560,18 @@ DummyProcKill(int code, Datum arg)
533560
/* Release any LW locks I am holding (see notes above) */
534561
LWLockReleaseAll();
535562

563+
SpinLockAcquire(ProcStructLock);
564+
536565
/* Mark dummy proc no longer in use */
537566
MyProc->pid = 0;
538567

539568
/* PGPROC struct isn't mine anymore */
540569
MyProc = NULL;
570+
571+
/* Update shared estimate of spins_per_delay */
572+
ProcGlobal->spins_per_delay = update_spins_per_delay(ProcGlobal->spins_per_delay);
573+
574+
SpinLockRelease(ProcStructLock);
541575
}
542576

543577

src/backend/storage/lmgr/s_lock.c

Lines changed: 99 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
*
1010
*
1111
* IDENTIFICATION
12-
* $PostgreSQL: pgsql/src/backend/storage/lmgr/s_lock.c,v 1.38 2005/08/26 14:47:35 tgl Exp $
12+
* $PostgreSQL: pgsql/src/backend/storage/lmgr/s_lock.c,v 1.39 2005/10/11 20:41:32 tgl Exp $
1313
*
1414
*-------------------------------------------------------------------------
1515
*/
@@ -21,6 +21,10 @@
2121
#include "storage/s_lock.h"
2222
#include "miscadmin.h"
2323

24+
25+
static int spins_per_delay = DEFAULT_SPINS_PER_DELAY;
26+
27+
2428
/*
2529
* s_lock_stuck() - complain about a stuck spinlock
2630
*/
@@ -49,54 +53,67 @@ s_lock(volatile slock_t *lock, const char *file, int line)
4953
* We loop tightly for awhile, then delay using pg_usleep() and try
5054
* again. Preferably, "awhile" should be a small multiple of the
5155
* maximum time we expect a spinlock to be held. 100 iterations seems
52-
* about right. In most multi-CPU scenarios, the spinlock is probably
53-
* held by a process on another CPU and will be released before we
54-
* finish 100 iterations. However, on a uniprocessor, the tight loop
55-
* is just a waste of cycles, so don't iterate thousands of times.
56+
* about right as an initial guess. However, on a uniprocessor the
57+
* loop is a waste of cycles, while in a multi-CPU scenario it's usually
58+
* better to spin a bit longer than to call the kernel, so we try to
59+
* adapt the spin loop count depending on whether we seem to be in
60+
* a uniprocessor or multiprocessor.
61+
*
62+
* Note: you might think MIN_SPINS_PER_DELAY should be just 1, but you'd
63+
* be wrong; there are platforms where that can result in a "stuck
64+
* spinlock" failure. This has been seen particularly on Alphas; it
65+
* seems that the first TAS after returning from kernel space will always
66+
* fail on that hardware.
5667
*
5768
* Once we do decide to block, we use randomly increasing pg_usleep()
58-
* delays. The first delay is 10 msec, then the delay randomly
59-
* increases to about one second, after which we reset to 10 msec and
69+
* delays. The first delay is 1 msec, then the delay randomly
70+
* increases to about one second, after which we reset to 1 msec and
6071
* start again. The idea here is that in the presence of heavy
6172
* contention we need to increase the delay, else the spinlock holder
6273
* may never get to run and release the lock. (Consider situation
6374
* where spinlock holder has been nice'd down in priority by the
6475
* scheduler --- it will not get scheduled until all would-be
65-
* acquirers are sleeping, so if we always use a 10-msec sleep, there
76+
* acquirers are sleeping, so if we always use a 1-msec sleep, there
6677
* is a real possibility of starvation.) But we can't just clamp the
6778
* delay to an upper bound, else it would take a long time to make a
6879
* reasonable number of tries.
6980
*
7081
* We time out and declare error after NUM_DELAYS delays (thus, exactly
7182
* that many tries). With the given settings, this will usually take
72-
* 3 or so minutes. It seems better to fix the total number of tries
83+
* 2 or so minutes. It seems better to fix the total number of tries
7384
* (and thus the probability of unintended failure) than to fix the
7485
* total time spent.
7586
*
76-
* The pg_usleep() delays are measured in centiseconds (0.01 sec) because
77-
* 10 msec is a common resolution limit at the OS level.
87+
* The pg_usleep() delays are measured in milliseconds because 1 msec
88+
* is a common resolution limit at the OS level for newer platforms.
89+
* On older platforms the resolution limit is usually 10 msec, in
90+
* which case the total delay before timeout will be a bit more.
7891
*/
79-
#define SPINS_PER_DELAY 100
92+
#define MIN_SPINS_PER_DELAY 10
93+
#define MAX_SPINS_PER_DELAY 1000
8094
#define NUM_DELAYS 1000
81-
#define MIN_DELAY_CSEC 1
82-
#define MAX_DELAY_CSEC 100
95+
#define MIN_DELAY_MSEC 1
96+
#define MAX_DELAY_MSEC 1000
8397

8498
int spins = 0;
8599
int delays = 0;
86-
int cur_delay = MIN_DELAY_CSEC;
100+
int cur_delay = 0;
87101

88102
while (TAS(lock))
89103
{
90104
/* CPU-specific delay each time through the loop */
91105
SPIN_DELAY();
92106

93-
/* Block the process every SPINS_PER_DELAY tries */
94-
if (++spins > SPINS_PER_DELAY)
107+
/* Block the process every spins_per_delay tries */
108+
if (++spins >= spins_per_delay)
95109
{
96110
if (++delays > NUM_DELAYS)
97111
s_lock_stuck(lock, file, line);
98112

99-
pg_usleep(cur_delay * 10000L);
113+
if (cur_delay == 0) /* first time to delay? */
114+
cur_delay = MIN_DELAY_MSEC;
115+
116+
pg_usleep(cur_delay * 1000L);
100117

101118
#if defined(S_LOCK_TEST)
102119
fprintf(stdout, "*");
@@ -107,14 +124,76 @@ s_lock(volatile slock_t *lock, const char *file, int line)
107124
cur_delay += (int) (cur_delay *
108125
(((double) random()) / ((double) MAX_RANDOM_VALUE)) + 0.5);
109126
/* wrap back to minimum delay when max is exceeded */
110-
if (cur_delay > MAX_DELAY_CSEC)
111-
cur_delay = MIN_DELAY_CSEC;
127+
if (cur_delay > MAX_DELAY_MSEC)
128+
cur_delay = MIN_DELAY_MSEC;
112129

113130
spins = 0;
114131
}
115132
}
133+
134+
/*
135+
* If we were able to acquire the lock without delaying, it's a good
136+
* indication we are in a multiprocessor. If we had to delay, it's
137+
* a sign (but not a sure thing) that we are in a uniprocessor.
138+
* Hence, we decrement spins_per_delay slowly when we had to delay,
139+
* and increase it rapidly when we didn't. It's expected that
140+
* spins_per_delay will converge to the minimum value on a uniprocessor
141+
* and to the maximum value on a multiprocessor.
142+
*
143+
* Note: spins_per_delay is local within our current process.
144+
* We want to average these observations across multiple backends,
145+
* since it's relatively rare for this function to even get entered,
146+
* and so a single backend might not live long enough to converge on
147+
* a good value. That is handled by the two routines below.
148+
*/
149+
if (cur_delay == 0)
150+
{
151+
/* we never had to delay */
152+
if (spins_per_delay < MAX_SPINS_PER_DELAY)
153+
spins_per_delay = Min(spins_per_delay + 100, MAX_SPINS_PER_DELAY);
154+
}
155+
else
156+
{
157+
if (spins_per_delay > MIN_SPINS_PER_DELAY)
158+
spins_per_delay = Max(spins_per_delay - 1, MIN_SPINS_PER_DELAY);
159+
}
160+
}
161+
162+
163+
/*
164+
* Set local copy of spins_per_delay during backend startup.
165+
*
166+
* NB: this has to be pretty fast as it is called while holding a spinlock
167+
*/
168+
void
169+
set_spins_per_delay(int shared_spins_per_delay)
170+
{
171+
spins_per_delay = shared_spins_per_delay;
172+
}
173+
174+
/*
175+
* Update shared estimate of spins_per_delay during backend exit.
176+
*
177+
* NB: this has to be pretty fast as it is called while holding a spinlock
178+
*/
179+
int
180+
update_spins_per_delay(int shared_spins_per_delay)
181+
{
182+
/*
183+
* We use an exponential moving average with a relatively slow
184+
* adaption rate, so that noise in any one backend's result won't
185+
* affect the shared value too much. As long as both inputs are
186+
* within the allowed range, the result must be too, so we need not
187+
* worry about clamping the result.
188+
*
189+
* We deliberately truncate rather than rounding; this is so that
190+
* single adjustments inside a backend can affect the shared estimate
191+
* (see the asymmetric adjustment rules above).
192+
*/
193+
return (shared_spins_per_delay * 15 + spins_per_delay) / 16;
116194
}
117195

196+
118197
/*
119198
* Various TAS implementations that cannot live in s_lock.h as no inline
120199
* definition exists (yet).

src/include/storage/proc.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
88
* Portions Copyright (c) 1994, Regents of the University of California
99
*
10-
* $PostgreSQL: pgsql/src/include/storage/proc.h,v 1.82 2005/09/19 17:21:48 momjian Exp $
10+
* $PostgreSQL: pgsql/src/include/storage/proc.h,v 1.83 2005/10/11 20:41:32 tgl Exp $
1111
*
1212
*-------------------------------------------------------------------------
1313
*/
@@ -105,6 +105,8 @@ typedef struct PROC_HDR
105105
{
106106
/* Head of list of free PGPROC structures */
107107
SHMEM_OFFSET freeProcs;
108+
/* Current shared estimate of appropriate spins_per_delay value */
109+
int spins_per_delay;
108110
} PROC_HDR;
109111

110112

src/include/storage/s_lock.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
6767
* Portions Copyright (c) 1994, Regents of the University of California
6868
*
69-
* $PostgreSQL: pgsql/src/include/storage/s_lock.h,v 1.141 2005/10/11 20:01:30 tgl Exp $
69+
* $PostgreSQL: pgsql/src/include/storage/s_lock.h,v 1.142 2005/10/11 20:41:32 tgl Exp $
7070
*
7171
*-------------------------------------------------------------------------
7272
*/
@@ -872,4 +872,10 @@ extern int tas(volatile slock_t *lock); /* in port/.../tas.s, or
872872
*/
873873
extern void s_lock(volatile slock_t *lock, const char *file, int line);
874874

875+
/* Support for dynamic adjustment of spins_per_delay */
876+
#define DEFAULT_SPINS_PER_DELAY 100
877+
878+
extern void set_spins_per_delay(int shared_spins_per_delay);
879+
extern int update_spins_per_delay(int shared_spins_per_delay);
880+
875881
#endif /* S_LOCK_H */

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy