diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c index 477982d..b907f72 100644 --- a/src/backend/access/transam/twophase.c +++ b/src/backend/access/transam/twophase.c @@ -114,6 +114,7 @@ int max_prepared_xacts = 0; typedef struct GlobalTransactionData { PGPROC proc; /* dummy proc */ + PGPROC_MINIMAL proc_minimal; /* dummy proc_minimal */ BackendId dummyBackendId; /* similar to backend id for backends */ TimestampTz prepared_at; /* time of preparation */ XLogRecPtr prepare_lsn; /* XLOG offset of prepare record */ @@ -223,6 +224,9 @@ TwoPhaseShmemInit(void) * technique. */ gxacts[i].dummyBackendId = MaxBackends + 1 + i; + + /* Initialize minimal proc structure from the global structure */ + gxacts[i].proc.proc_minimal = &gxacts[i].proc_minimal; } } else @@ -310,14 +314,15 @@ MarkAsPreparing(TransactionId xid, const char *gid, gxact->proc.waitStatus = STATUS_OK; /* We set up the gxact's VXID as InvalidBackendId/XID */ gxact->proc.lxid = (LocalTransactionId) xid; - gxact->proc.xid = xid; - gxact->proc.xmin = InvalidTransactionId; + gxact->proc.proc_minimal = &gxact->proc_minimal; + gxact->proc.proc_minimal->xid = xid; + gxact->proc.proc_minimal->xmin = InvalidTransactionId; gxact->proc.pid = 0; gxact->proc.backendId = InvalidBackendId; gxact->proc.databaseId = databaseid; gxact->proc.roleId = owner; - gxact->proc.inCommit = false; - gxact->proc.vacuumFlags = 0; + gxact->proc.proc_minimal->inCommit = false; + gxact->proc.proc_minimal->vacuumFlags = 0; gxact->proc.lwWaiting = false; gxact->proc.lwExclusive = false; gxact->proc.lwWaitLink = NULL; @@ -326,8 +331,8 @@ MarkAsPreparing(TransactionId xid, const char *gid, for (i = 0; i < NUM_LOCK_PARTITIONS; i++) SHMQueueInit(&(gxact->proc.myProcLocks[i])); /* subxid data must be filled later by GXactLoadSubxactData */ - gxact->proc.subxids.overflowed = false; - gxact->proc.subxids.nxids = 0; + gxact->proc.proc_minimal->overflowed = false; + gxact->proc.proc_minimal->nxids = 0; gxact->prepared_at = prepared_at; /* initialize LSN to 0 (start of WAL) */ @@ -361,14 +366,14 @@ GXactLoadSubxactData(GlobalTransaction gxact, int nsubxacts, /* We need no extra lock since the GXACT isn't valid yet */ if (nsubxacts > PGPROC_MAX_CACHED_SUBXIDS) { - gxact->proc.subxids.overflowed = true; + gxact->proc.proc_minimal->overflowed = true; nsubxacts = PGPROC_MAX_CACHED_SUBXIDS; } if (nsubxacts > 0) { memcpy(gxact->proc.subxids.xids, children, nsubxacts * sizeof(TransactionId)); - gxact->proc.subxids.nxids = nsubxacts; + gxact->proc.proc_minimal->nxids = nsubxacts; } } @@ -519,7 +524,7 @@ TransactionIdIsPrepared(TransactionId xid) { GlobalTransaction gxact = TwoPhaseState->prepXacts[i]; - if (gxact->valid && gxact->proc.xid == xid) + if (gxact->valid && gxact->proc_minimal.xid == xid) { result = true; break; @@ -656,7 +661,7 @@ pg_prepared_xact(PG_FUNCTION_ARGS) MemSet(values, 0, sizeof(values)); MemSet(nulls, 0, sizeof(nulls)); - values[0] = TransactionIdGetDatum(gxact->proc.xid); + values[0] = TransactionIdGetDatum(gxact->proc_minimal.xid); values[1] = CStringGetTextDatum(gxact->gid); values[2] = TimestampTzGetDatum(gxact->prepared_at); values[3] = ObjectIdGetDatum(gxact->owner); @@ -712,7 +717,7 @@ TwoPhaseGetDummyProc(TransactionId xid) { GlobalTransaction gxact = TwoPhaseState->prepXacts[i]; - if (gxact->proc.xid == xid) + if (gxact->proc_minimal.xid == xid) { result = &gxact->proc; break; @@ -841,7 +846,7 @@ save_state_data(const void *data, uint32 len) void StartPrepare(GlobalTransaction gxact) { - TransactionId xid = gxact->proc.xid; + TransactionId xid = gxact->proc_minimal.xid; TwoPhaseFileHeader hdr; TransactionId *children; RelFileNode *commitrels; @@ -913,7 +918,7 @@ StartPrepare(GlobalTransaction gxact) void EndPrepare(GlobalTransaction gxact) { - TransactionId xid = gxact->proc.xid; + TransactionId xid = gxact->proc_minimal.xid; TwoPhaseFileHeader *hdr; char path[MAXPGPATH]; XLogRecData *record; @@ -1021,7 +1026,7 @@ EndPrepare(GlobalTransaction gxact) */ START_CRIT_SECTION(); - MyProc->inCommit = true; + MyProc->proc_minimal->inCommit = true; gxact->prepare_lsn = XLogInsert(RM_XACT_ID, XLOG_XACT_PREPARE, records.head); @@ -1069,7 +1074,7 @@ EndPrepare(GlobalTransaction gxact) * checkpoint starting after this will certainly see the gxact as a * candidate for fsyncing. */ - MyProc->inCommit = false; + MyProc->proc_minimal->inCommit = false; END_CRIT_SECTION(); @@ -1260,7 +1265,7 @@ FinishPreparedTransaction(const char *gid, bool isCommit) * try to commit the same GID at once. */ gxact = LockGXact(gid, GetUserId()); - xid = gxact->proc.xid; + xid = gxact->proc_minimal.xid; /* * Read and validate the state file @@ -1543,7 +1548,7 @@ CheckPointTwoPhase(XLogRecPtr redo_horizon) if (gxact->valid && XLByteLE(gxact->prepare_lsn, redo_horizon)) - xids[nxids++] = gxact->proc.xid; + xids[nxids++] = gxact->proc_minimal.xid; } LWLockRelease(TwoPhaseStateLock); @@ -1972,7 +1977,7 @@ RecordTransactionCommitPrepared(TransactionId xid, START_CRIT_SECTION(); /* See notes in RecordTransactionCommit */ - MyProc->inCommit = true; + MyProc->proc_minimal->inCommit = true; /* Emit the XLOG commit record */ xlrec.xid = xid; @@ -2037,7 +2042,7 @@ RecordTransactionCommitPrepared(TransactionId xid, TransactionIdCommitTree(xid, nchildren, children); /* Checkpoint can proceed now */ - MyProc->inCommit = false; + MyProc->proc_minimal->inCommit = false; END_CRIT_SECTION(); diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c index 61dcfed..0effa1a 100644 --- a/src/backend/access/transam/varsup.c +++ b/src/backend/access/transam/varsup.c @@ -54,7 +54,7 @@ GetNewTransactionId(bool isSubXact) if (IsBootstrapProcessingMode()) { Assert(!isSubXact); - MyProc->xid = BootstrapTransactionId; + MyProc->proc_minimal->xid = BootstrapTransactionId; return BootstrapTransactionId; } @@ -210,18 +210,18 @@ GetNewTransactionId(bool isSubXact) volatile PGPROC *myproc = MyProc; if (!isSubXact) - myproc->xid = xid; + myproc->proc_minimal->xid = xid; else { - int nxids = myproc->subxids.nxids; + int nxids = myproc->proc_minimal->nxids; if (nxids < PGPROC_MAX_CACHED_SUBXIDS) { myproc->subxids.xids[nxids] = xid; - myproc->subxids.nxids = nxids + 1; + myproc->proc_minimal->nxids = nxids + 1; } else - myproc->subxids.overflowed = true; + myproc->proc_minimal->overflowed = true; } } diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index c151d3b..838bd23 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -981,7 +981,7 @@ RecordTransactionCommit(void) * bit fuzzy, but it doesn't matter. */ START_CRIT_SECTION(); - MyProc->inCommit = true; + MyProc->proc_minimal->inCommit = true; SetCurrentTransactionStopTimestamp(); @@ -1155,7 +1155,7 @@ RecordTransactionCommit(void) */ if (markXidCommitted) { - MyProc->inCommit = false; + MyProc->proc_minimal->inCommit = false; END_CRIT_SECTION(); } diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index 32985a4..a6ee452 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -223,7 +223,7 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy) * OK, let's do it. First let other backends know I'm in ANALYZE. */ LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); - MyProc->vacuumFlags |= PROC_IN_ANALYZE; + MyProc->proc_minimal->vacuumFlags |= PROC_IN_ANALYZE; LWLockRelease(ProcArrayLock); /* @@ -250,7 +250,7 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy) * because the vacuum flag is cleared by the end-of-xact code. */ LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); - MyProc->vacuumFlags &= ~PROC_IN_ANALYZE; + MyProc->proc_minimal->vacuumFlags &= ~PROC_IN_ANALYZE; LWLockRelease(ProcArrayLock); } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index f42504c..c85c002 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -893,9 +893,9 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool do_toast, bool for_wraparound) * which is probably Not Good. */ LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); - MyProc->vacuumFlags |= PROC_IN_VACUUM; + MyProc->proc_minimal->vacuumFlags |= PROC_IN_VACUUM; if (for_wraparound) - MyProc->vacuumFlags |= PROC_VACUUM_FOR_WRAPAROUND; + MyProc->proc_minimal->vacuumFlags |= PROC_VACUUM_FOR_WRAPAROUND; LWLockRelease(ProcArrayLock); } diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index dd2d6ee..aa54a98 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -702,7 +702,7 @@ ProcessStandbyHSFeedbackMessage(void) * safe, and if we're moving it backwards, well, the data is at risk * already since a VACUUM could have just finished calling GetOldestXmin.) */ - MyProc->xmin = reply.xmin; + MyProc->proc_minimal->xmin = reply.xmin; } /* Main loop of walsender process */ diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c index 1a48485..f0e1c5a 100644 --- a/src/backend/storage/ipc/procarray.c +++ b/src/backend/storage/ipc/procarray.c @@ -141,6 +141,29 @@ static void DisplayXidCache(void); #define xc_slow_answer_inc() ((void) 0) #endif /* XIDCACHE_DEBUG */ +/* + * Get the PROC_MINIMAL structure corresponding to the given PGPROC. The trick + * is to avoid access to any PGPROC member to find the minimal structure + * because we want to avoid access to any PGPROC member unless its absolutely + * necessary. This reduces cache misses and faults. Except for dummy procs for + * prepared transactions, all other procs have a corresponding entry in the + * allProcs_Minimal array of PGPROC_MINIMAL. So we use pointer arithmetic to + * get that. + * + * XXX A branch mis-prediction can still end up touching the PGPROC + * proc_minimal member and that would cause cache miss. We have seen this on + * HP-UX compiler. It might be easier to handle with GCC by use of + * likely/unlikely hint knowing that in almost all cases its not needed to go + * to the PGPROC member or by rearranging the following code. For example, we + * can return (PGPROC_MINIMAL **) to avoid accessing PGPROC structure unless + * its required for prepared transaction + */ +#define PGProcGetMinimal(proc, procglobal) \ + ((proc) >= (procglobal)->allProcs && \ + (proc) < (procglobal)->allProcs + (procglobal)->allProcCount) ? \ + &(procglobal)->allProcs_Minimal[(proc) - (procglobal)->allProcs] : \ + (proc)->proc_minimal; + /* Primitives for KnownAssignedXids array handling for standby */ static void KnownAssignedXidsCompress(bool force); static void KnownAssignedXidsAdd(TransactionId from_xid, TransactionId to_xid, @@ -253,6 +276,7 @@ void ProcArrayAdd(PGPROC *proc) { ProcArrayStruct *arrayP = procArray; + int index; LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); @@ -269,7 +293,28 @@ ProcArrayAdd(PGPROC *proc) errmsg("sorry, too many clients already"))); } - arrayP->procs[arrayP->numProcs] = proc; + /* + * Keep the procs array sorted by (PGPROC *) so that we can utilize + * locality of references much better. This is useful while traversing the + * ProcArray because there is a increased likelyhood of finding the next + * PGPROC structure in the cache. + * + * Since the occurance of adding/removing a proc is much lower than the + * access to the ProcArray itself, the overhead should be marginal + */ + for (index = 0; index < arrayP->numProcs; index++) + { + /* + * If we are the first PGPROC or if we have found our right position in + * the array, break + */ + if ((arrayP->procs[index] == NULL) || (arrayP->procs[index] > proc)) + break; + } + + memmove(&arrayP->procs[index + 1], &arrayP->procs[index], + (arrayP->numProcs - index) * sizeof (PGPROC *)); + arrayP->procs[index] = proc; arrayP->numProcs++; LWLockRelease(ProcArrayLock); @@ -318,7 +363,9 @@ ProcArrayRemove(PGPROC *proc, TransactionId latestXid) { if (arrayP->procs[index] == proc) { - arrayP->procs[index] = arrayP->procs[arrayP->numProcs - 1]; + /* Keep the PGPROC array sorted. See notes above */ + memmove(&arrayP->procs[index], &arrayP->procs[index + 1], + (arrayP->numProcs - index - 1) * sizeof (PGPROC *)); arrayP->procs[arrayP->numProcs - 1] = NULL; /* for debugging */ arrayP->numProcs--; LWLockRelease(ProcArrayLock); @@ -361,17 +408,17 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid) LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); - proc->xid = InvalidTransactionId; + proc->proc_minimal->xid = InvalidTransactionId; proc->lxid = InvalidLocalTransactionId; - proc->xmin = InvalidTransactionId; + proc->proc_minimal->xmin = InvalidTransactionId; /* must be cleared with xid/xmin: */ - proc->vacuumFlags &= ~PROC_VACUUM_STATE_MASK; - proc->inCommit = false; /* be sure this is cleared in abort */ + proc->proc_minimal->vacuumFlags &= ~PROC_VACUUM_STATE_MASK; + proc->proc_minimal->inCommit = false; /* be sure this is cleared in abort */ proc->recoveryConflictPending = false; /* Clear the subtransaction-XID cache too while holding the lock */ - proc->subxids.nxids = 0; - proc->subxids.overflowed = false; + proc->proc_minimal->nxids = 0; + proc->proc_minimal->overflowed = false; /* Also advance global latestCompletedXid while holding the lock */ if (TransactionIdPrecedes(ShmemVariableCache->latestCompletedXid, @@ -390,10 +437,10 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid) Assert(!TransactionIdIsValid(proc->xid)); proc->lxid = InvalidLocalTransactionId; - proc->xmin = InvalidTransactionId; + proc->proc_minimal->xmin = InvalidTransactionId; /* must be cleared with xid/xmin: */ - proc->vacuumFlags &= ~PROC_VACUUM_STATE_MASK; - proc->inCommit = false; /* be sure this is cleared in abort */ + proc->proc_minimal->vacuumFlags &= ~PROC_VACUUM_STATE_MASK; + proc->proc_minimal->inCommit = false; /* be sure this is cleared in abort */ proc->recoveryConflictPending = false; Assert(proc->subxids.nxids == 0); @@ -419,18 +466,18 @@ ProcArrayClearTransaction(PGPROC *proc) * duplicate with the gxact that has already been inserted into the * ProcArray. */ - proc->xid = InvalidTransactionId; + proc->proc_minimal->xid = InvalidTransactionId; proc->lxid = InvalidLocalTransactionId; - proc->xmin = InvalidTransactionId; + proc->proc_minimal->xmin = InvalidTransactionId; proc->recoveryConflictPending = false; /* redundant, but just in case */ - proc->vacuumFlags &= ~PROC_VACUUM_STATE_MASK; - proc->inCommit = false; + proc->proc_minimal->vacuumFlags &= ~PROC_VACUUM_STATE_MASK; + proc->proc_minimal->inCommit = false; /* Clear the subtransaction-XID cache too */ - proc->subxids.nxids = 0; - proc->subxids.overflowed = false; + proc->proc_minimal->nxids = 0; + proc->proc_minimal->overflowed = false; } /* @@ -741,6 +788,10 @@ TransactionIdIsInProgress(TransactionId xid) TransactionId topxid; int i, j; +#ifdef NOT_USED + static PGPROC **procs = NULL; + static PGPROC_MINIMAL *txns = NULL; +#endif /* * Don't bother checking a transaction older than RecentXmin; it could not @@ -811,15 +862,19 @@ TransactionIdIsInProgress(TransactionId xid) /* No shortcuts, gotta grovel through the array */ for (i = 0; i < arrayP->numProcs; i++) { + volatile PROC_HDR *procglobal = ProcGlobal; volatile PGPROC *proc = arrayP->procs[i]; + volatile PGPROC_MINIMAL *proc_minimal; TransactionId pxid; /* Ignore my own proc --- dealt with it above */ if (proc == MyProc) continue; + proc_minimal = PGProcGetMinimal(proc, procglobal); + /* Fetch xid just once - see GetNewTransactionId */ - pxid = proc->xid; + pxid = proc_minimal->xid; if (!TransactionIdIsValid(pxid)) continue; @@ -844,7 +899,7 @@ TransactionIdIsInProgress(TransactionId xid) /* * Step 2: check the cached child-Xids arrays */ - for (j = proc->subxids.nxids - 1; j >= 0; j--) + for (j = proc_minimal->nxids - 1; j >= 0; j--) { /* Fetch xid just once - see GetNewTransactionId */ TransactionId cxid = proc->subxids.xids[j]; @@ -864,7 +919,7 @@ TransactionIdIsInProgress(TransactionId xid) * we hold ProcArrayLock. So we can't miss an Xid that we need to * worry about.) */ - if (proc->subxids.overflowed) + if (proc_minimal->overflowed) xids[nxids++] = pxid; } @@ -965,10 +1020,15 @@ TransactionIdIsActive(TransactionId xid) for (i = 0; i < arrayP->numProcs; i++) { + volatile PROC_HDR *procglobal = ProcGlobal; volatile PGPROC *proc = arrayP->procs[i]; + volatile PGPROC_MINIMAL *proc_minimal; + TransactionId pxid; + + proc_minimal = PGProcGetMinimal(proc, procglobal); /* Fetch xid just once - see GetNewTransactionId */ - TransactionId pxid = proc->xid; + pxid = proc_minimal->xid; if (!TransactionIdIsValid(pxid)) continue; @@ -1060,9 +1120,13 @@ GetOldestXmin(bool allDbs, bool ignoreVacuum) for (index = 0; index < arrayP->numProcs; index++) { + volatile PROC_HDR *procglobal = ProcGlobal; volatile PGPROC *proc = arrayP->procs[index]; + volatile PGPROC_MINIMAL *proc_minimal; + + proc_minimal = PGProcGetMinimal(proc, procglobal); - if (ignoreVacuum && (proc->vacuumFlags & PROC_IN_VACUUM)) + if (ignoreVacuum && (proc_minimal->vacuumFlags & PROC_IN_VACUUM)) continue; if (allDbs || @@ -1070,7 +1134,7 @@ GetOldestXmin(bool allDbs, bool ignoreVacuum) proc->databaseId == 0) /* always include WalSender */ { /* Fetch xid just once - see GetNewTransactionId */ - TransactionId xid = proc->xid; + TransactionId xid = proc_minimal->xid; /* First consider the transaction's own Xid, if any */ if (TransactionIdIsNormal(xid) && @@ -1084,7 +1148,7 @@ GetOldestXmin(bool allDbs, bool ignoreVacuum) * have an Xmin but not (yet) an Xid; conversely, if it has an * Xid, that could determine some not-yet-set Xmin. */ - xid = proc->xmin; /* Fetch just once */ + xid = proc_minimal->xmin; /* Fetch just once */ if (TransactionIdIsNormal(xid) && TransactionIdPrecedes(xid, result)) result = xid; @@ -1271,21 +1335,35 @@ GetSnapshotData(Snapshot snapshot) */ for (index = 0; index < arrayP->numProcs; index++) { + volatile PROC_HDR *procglobal = ProcGlobal; volatile PGPROC *proc = arrayP->procs[index]; + volatile PGPROC_MINIMAL *proc_minimal; TransactionId xid; + /* + * All the information needed by GetSnapshotData is stored out of + * band as an array of PGPROC_MINIMAL structures. The only exception + * is the dummy PGPROC for prepared transaction. + * + * We try to avoid accessing any field from the PGPROC because that + * may cause cache miss and associated overhead. We instead try to + * directly access the relevant information by doing pointer + * arithmatic. + */ + proc_minimal = PGProcGetMinimal(proc, procglobal); + /* Ignore procs running LAZY VACUUM */ - if (proc->vacuumFlags & PROC_IN_VACUUM) + if (proc_minimal->vacuumFlags & PROC_IN_VACUUM) continue; /* Update globalxmin to be the smallest valid xmin */ - xid = proc->xmin; /* fetch just once */ + xid = proc_minimal->xmin; /* fetch just once */ if (TransactionIdIsNormal(xid) && TransactionIdPrecedes(xid, globalxmin)) globalxmin = xid; /* Fetch xid just once - see GetNewTransactionId */ - xid = proc->xid; + xid = proc_minimal->xid; /* * If the transaction has been assigned an xid < xmax we add it to @@ -1323,11 +1401,11 @@ GetSnapshotData(Snapshot snapshot) */ if (!suboverflowed && proc != MyProc) { - if (proc->subxids.overflowed) + if (proc_minimal->overflowed) suboverflowed = true; else { - int nxids = proc->subxids.nxids; + int nxids = proc_minimal->nxids; if (nxids > 0) { @@ -1372,9 +1450,8 @@ GetSnapshotData(Snapshot snapshot) suboverflowed = true; } - if (!TransactionIdIsValid(MyProc->xmin)) - MyProc->xmin = TransactionXmin = xmin; - + if (!TransactionIdIsValid(MyProc->proc_minimal->xmin)) + MyProc->proc_minimal->xmin = TransactionXmin = xmin; LWLockRelease(ProcArrayLock); /* @@ -1436,14 +1513,18 @@ ProcArrayInstallImportedXmin(TransactionId xmin, TransactionId sourcexid) for (index = 0; index < arrayP->numProcs; index++) { + volatile PROC_HDR *procglobal = ProcGlobal; volatile PGPROC *proc = arrayP->procs[index]; + volatile PGPROC_MINIMAL *proc_minimal; TransactionId xid; + proc_minimal = PGProcGetMinimal(proc, procglobal); + /* Ignore procs running LAZY VACUUM */ - if (proc->vacuumFlags & PROC_IN_VACUUM) + if (proc_minimal->vacuumFlags & PROC_IN_VACUUM) continue; - xid = proc->xid; /* fetch just once */ + xid = proc_minimal->xid; /* fetch just once */ if (xid != sourcexid) continue; @@ -1459,7 +1540,7 @@ ProcArrayInstallImportedXmin(TransactionId xmin, TransactionId sourcexid) /* * Likewise, let's just make real sure its xmin does cover us. */ - xid = proc->xmin; /* fetch just once */ + xid = proc_minimal->xmin; /* fetch just once */ if (!TransactionIdIsNormal(xid) || !TransactionIdPrecedesOrEquals(xid, xmin)) continue; @@ -1470,7 +1551,7 @@ ProcArrayInstallImportedXmin(TransactionId xmin, TransactionId sourcexid) * GetSnapshotData first, we'll be overwriting a valid xmin here, * so we don't check that.) */ - MyProc->xmin = TransactionXmin = xmin; + MyProc->proc_minimal->xmin = TransactionXmin = xmin; result = true; break; @@ -1562,12 +1643,16 @@ GetRunningTransactionData(void) */ for (index = 0; index < arrayP->numProcs; index++) { + volatile PROC_HDR *procglobal = ProcGlobal; volatile PGPROC *proc = arrayP->procs[index]; + volatile PGPROC_MINIMAL *proc_minimal; TransactionId xid; int nxids; + proc_minimal = PGProcGetMinimal(proc, procglobal); + /* Fetch xid just once - see GetNewTransactionId */ - xid = proc->xid; + xid = proc_minimal->xid; /* * We don't need to store transactions that don't have a TransactionId @@ -1585,7 +1670,7 @@ GetRunningTransactionData(void) * Save subtransaction XIDs. Other backends can't add or remove * entries while we're holding XidGenLock. */ - nxids = proc->subxids.nxids; + nxids = proc_minimal->nxids; if (nxids > 0) { memcpy(&xids[count], (void *) proc->subxids.xids, @@ -1593,7 +1678,7 @@ GetRunningTransactionData(void) count += nxids; subcount += nxids; - if (proc->subxids.overflowed) + if (proc_minimal->overflowed) suboverflowed = true; /* @@ -1653,11 +1738,15 @@ GetOldestActiveTransactionId(void) */ for (index = 0; index < arrayP->numProcs; index++) { + volatile PROC_HDR *procglobal = ProcGlobal; volatile PGPROC *proc = arrayP->procs[index]; + volatile PGPROC_MINIMAL *proc_minimal; TransactionId xid; + proc_minimal = PGProcGetMinimal(proc, procglobal); + /* Fetch xid just once - see GetNewTransactionId */ - xid = proc->xid; + xid = proc_minimal->xid; if (!TransactionIdIsNormal(xid)) continue; @@ -1709,12 +1798,17 @@ GetTransactionsInCommit(TransactionId **xids_p) for (index = 0; index < arrayP->numProcs; index++) { + volatile PROC_HDR *procglobal = ProcGlobal; volatile PGPROC *proc = arrayP->procs[index]; + volatile PGPROC_MINIMAL *proc_minimal; + TransactionId pxid; + + proc_minimal = PGProcGetMinimal(proc, procglobal); /* Fetch xid just once - see GetNewTransactionId */ - TransactionId pxid = proc->xid; + pxid = proc_minimal->xid; - if (proc->inCommit && TransactionIdIsValid(pxid)) + if (proc_minimal->inCommit && TransactionIdIsValid(pxid)) xids[nxids++] = pxid; } @@ -1744,12 +1838,17 @@ HaveTransactionsInCommit(TransactionId *xids, int nxids) for (index = 0; index < arrayP->numProcs; index++) { + volatile PROC_HDR *procglobal = ProcGlobal; volatile PGPROC *proc = arrayP->procs[index]; + volatile PGPROC_MINIMAL *proc_minimal; + TransactionId pxid; + + proc_minimal = PGProcGetMinimal(proc, procglobal); /* Fetch xid just once - see GetNewTransactionId */ - TransactionId pxid = proc->xid; + pxid = proc_minimal->xid; - if (proc->inCommit && TransactionIdIsValid(pxid)) + if (proc_minimal->inCommit && TransactionIdIsValid(pxid)) { int i; @@ -1833,9 +1932,13 @@ BackendXidGetPid(TransactionId xid) for (index = 0; index < arrayP->numProcs; index++) { + volatile PROC_HDR *procglobal = ProcGlobal; volatile PGPROC *proc = arrayP->procs[index]; + volatile PGPROC_MINIMAL *proc_minimal; - if (proc->xid == xid) + proc_minimal = PGProcGetMinimal(proc, procglobal); + + if (proc_minimal->xid == xid) { result = proc->pid; break; @@ -1906,13 +2009,13 @@ GetCurrentVirtualXIDs(TransactionId limitXmin, bool excludeXmin0, if (proc == MyProc) continue; - if (excludeVacuum & proc->vacuumFlags) + if (excludeVacuum & proc->proc_minimal->vacuumFlags) continue; if (allDbs || proc->databaseId == MyDatabaseId) { /* Fetch xmin just once - might change on us */ - TransactionId pxmin = proc->xmin; + TransactionId pxmin = proc->proc_minimal->xmin; if (excludeXmin0 && !TransactionIdIsValid(pxmin)) continue; @@ -2006,7 +2109,7 @@ GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid) proc->databaseId == dbOid) { /* Fetch xmin just once - can't change on us, but good coding */ - TransactionId pxmin = proc->xmin; + TransactionId pxmin = proc->proc_minimal->xmin; /* * We ignore an invalid pxmin because this means that backend has @@ -2104,7 +2207,9 @@ MinimumActiveBackends(int min) */ for (index = 0; index < arrayP->numProcs; index++) { + volatile PROC_HDR *procglobal = ProcGlobal; volatile PGPROC *proc = arrayP->procs[index]; + volatile PGPROC_MINIMAL *proc_minimal; /* * Since we're not holding a lock, need to check that the pointer is @@ -2120,12 +2225,14 @@ MinimumActiveBackends(int min) if (proc == NULL) continue; + proc_minimal = PGProcGetMinimal(proc, procglobal); + if (proc == MyProc) continue; /* do not count myself */ + if (proc_minimal->xid == InvalidTransactionId) + continue; /* do not count if no XID assigned */ if (proc->pid == 0) continue; /* do not count prepared xacts */ - if (proc->xid == InvalidTransactionId) - continue; /* do not count if no XID assigned */ if (proc->waitLock != NULL) continue; /* do not count if blocked on a lock */ count++; @@ -2291,7 +2398,7 @@ CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared) else { (*nbackends)++; - if ((proc->vacuumFlags & PROC_IS_AUTOVACUUM) && + if ((proc->proc_minimal->vacuumFlags & PROC_IS_AUTOVACUUM) && nautovacs < MAXAUTOVACPIDS) autovac_pids[nautovacs++] = proc->pid; } @@ -2321,8 +2428,8 @@ CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared) #define XidCacheRemove(i) \ do { \ - MyProc->subxids.xids[i] = MyProc->subxids.xids[MyProc->subxids.nxids - 1]; \ - MyProc->subxids.nxids--; \ + MyProc->subxids.xids[i] = MyProc->subxids.xids[MyProc->proc_minimal->nxids - 1]; \ + MyProc->proc_minimal->nxids--; \ } while (0) /* @@ -2361,7 +2468,7 @@ XidCacheRemoveRunningXids(TransactionId xid, { TransactionId anxid = xids[i]; - for (j = MyProc->subxids.nxids - 1; j >= 0; j--) + for (j = MyProc->proc_minimal->nxids - 1; j >= 0; j--) { if (TransactionIdEquals(MyProc->subxids.xids[j], anxid)) { @@ -2377,11 +2484,11 @@ XidCacheRemoveRunningXids(TransactionId xid, * error during AbortSubTransaction. So instead of Assert, emit a * debug warning. */ - if (j < 0 && !MyProc->subxids.overflowed) + if (j < 0 && !MyProc->proc_minimal->overflowed) elog(WARNING, "did not find subXID %u in MyProc", anxid); } - for (j = MyProc->subxids.nxids - 1; j >= 0; j--) + for (j = MyProc->proc_minimal->nxids - 1; j >= 0; j--) { if (TransactionIdEquals(MyProc->subxids.xids[j], xid)) { @@ -2390,7 +2497,7 @@ XidCacheRemoveRunningXids(TransactionId xid, } } /* Ordinarily we should have found it, unless the cache has overflowed */ - if (j < 0 && !MyProc->subxids.overflowed) + if (j < 0 && !MyProc->proc_minimal->overflowed) elog(WARNING, "did not find subXID %u in MyProc", xid); /* Also advance global latestCompletedXid while holding the lock */ diff --git a/src/backend/storage/lmgr/deadlock.c b/src/backend/storage/lmgr/deadlock.c index 7e7f6af..bb1e654 100644 --- a/src/backend/storage/lmgr/deadlock.c +++ b/src/backend/storage/lmgr/deadlock.c @@ -541,7 +541,7 @@ FindLockCycleRecurse(PGPROC *checkProc, * vacuumFlag bit), but we don't do that here to avoid * grabbing ProcArrayLock. */ - if (proc->vacuumFlags & PROC_IS_AUTOVACUUM) + if (proc->proc_minimal->vacuumFlags & PROC_IS_AUTOVACUUM) blocking_autovacuum_proc = proc; /* This proc hard-blocks checkProc */ diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index ed8344f..3e7a557 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -3184,7 +3184,7 @@ GetRunningTransactionLocks(int *nlocks) PGPROC *proc = proclock->tag.myProc; LOCK *lock = proclock->tag.myLock; - accessExclusiveLocks[index].xid = proc->xid; + accessExclusiveLocks[index].xid = proc->proc_minimal->xid; accessExclusiveLocks[index].dbOid = lock->tag.locktag_field1; accessExclusiveLocks[index].relOid = lock->tag.locktag_field2; diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index eda3a98..0261de7 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -113,6 +113,9 @@ ProcGlobalShmemSize(void) /* ProcStructLock */ size = add_size(size, sizeof(slock_t)); + size = add_size(size, mul_size(NUM_AUXILIARY_PROCS, sizeof(PGPROC_MINIMAL))); + size = add_size(size, mul_size(MaxBackends, sizeof(PGPROC_MINIMAL))); + return size; } @@ -157,6 +160,7 @@ void InitProcGlobal(void) { PGPROC *procs; + PGPROC_MINIMAL *procs_minimal; int i, j; bool found; @@ -195,6 +199,22 @@ InitProcGlobal(void) (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of shared memory"))); MemSet(procs, 0, TotalProcs * sizeof(PGPROC)); + + /* + * Also allocate a separate array of PROC_MINIMAL structures. We keep this + * out of band of the main PGPROC array to ensure the very heavily accessed + * members of the PGPROC structure are stored contiguously in the memory. + * This provides significant performance benefits, especially on a + * multiprocessor system by improving cache hit ratio. + * + * Note: We separate the members needed by GetSnapshotData since that's the + * most frequently accessed code path. There is one PROC_MINIMAL structure + * for every PGPROC structure. + */ + procs_minimal = (PGPROC_MINIMAL *) ShmemAlloc(TotalProcs * sizeof(PGPROC_MINIMAL)); + MemSet(procs_minimal, 0, TotalProcs * sizeof(PGPROC_MINIMAL)); + ProcGlobal->allProcs_Minimal = procs_minimal; + for (i = 0; i < TotalProcs; i++) { /* Common initialization for all PGPROCs, regardless of type. */ @@ -203,6 +223,7 @@ InitProcGlobal(void) PGSemaphoreCreate(&(procs[i].sem)); InitSharedLatch(&(procs[i].procLatch)); procs[i].backendLock = LWLockAssign(); + procs[i].proc_minimal = &procs_minimal[i]; /* * Newly created PGPROCs for normal backends or for autovacuum must @@ -313,18 +334,18 @@ InitProcess(void) SHMQueueElemInit(&(MyProc->links)); MyProc->waitStatus = STATUS_OK; MyProc->lxid = InvalidLocalTransactionId; - MyProc->xid = InvalidTransactionId; - MyProc->xmin = InvalidTransactionId; + MyProc->proc_minimal->xid = InvalidTransactionId; + MyProc->proc_minimal->xmin = InvalidTransactionId; MyProc->pid = MyProcPid; /* backendId, databaseId and roleId will be filled in later */ MyProc->backendId = InvalidBackendId; MyProc->databaseId = InvalidOid; MyProc->roleId = InvalidOid; - MyProc->inCommit = false; - MyProc->vacuumFlags = 0; + MyProc->proc_minimal->inCommit = false; + MyProc->proc_minimal->vacuumFlags = 0; /* NB -- autovac launcher intentionally does not set IS_AUTOVACUUM */ if (IsAutoVacuumWorkerProcess()) - MyProc->vacuumFlags |= PROC_IS_AUTOVACUUM; + MyProc->proc_minimal->vacuumFlags |= PROC_IS_AUTOVACUUM; MyProc->lwWaiting = false; MyProc->lwExclusive = false; MyProc->lwWaitLink = NULL; @@ -472,13 +493,13 @@ InitAuxiliaryProcess(void) SHMQueueElemInit(&(MyProc->links)); MyProc->waitStatus = STATUS_OK; MyProc->lxid = InvalidLocalTransactionId; - MyProc->xid = InvalidTransactionId; - MyProc->xmin = InvalidTransactionId; + MyProc->proc_minimal->xid = InvalidTransactionId; + MyProc->proc_minimal->xmin = InvalidTransactionId; MyProc->backendId = InvalidBackendId; MyProc->databaseId = InvalidOid; MyProc->roleId = InvalidOid; - MyProc->inCommit = false; - MyProc->vacuumFlags = 0; + MyProc->proc_minimal->inCommit = false; + MyProc->proc_minimal->vacuumFlags = 0; MyProc->lwWaiting = false; MyProc->lwExclusive = false; MyProc->lwWaitLink = NULL; @@ -1053,8 +1074,8 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable) * wraparound. */ if ((autovac != NULL) && - (autovac->vacuumFlags & PROC_IS_AUTOVACUUM) && - !(autovac->vacuumFlags & PROC_VACUUM_FOR_WRAPAROUND)) + (autovac->proc_minimal->vacuumFlags & PROC_IS_AUTOVACUUM) && + !(autovac->proc_minimal->vacuumFlags & PROC_VACUUM_FOR_WRAPAROUND)) { int pid = autovac->pid; diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c index 50fb780..bcfe3f3 100644 --- a/src/backend/utils/time/snapmgr.c +++ b/src/backend/utils/time/snapmgr.c @@ -577,7 +577,7 @@ static void SnapshotResetXmin(void) { if (RegisteredSnapshots == 0 && ActiveSnapshot == NULL) - MyProc->xmin = InvalidTransactionId; + MyProc->proc_minimal->xmin = InvalidTransactionId; } /* diff --git a/src/include/storage/lock.h b/src/include/storage/lock.h index bc746a3..093a56e 100644 --- a/src/include/storage/lock.h +++ b/src/include/storage/lock.h @@ -21,6 +21,7 @@ /* struct PGPROC is declared in proc.h, but must forward-reference it */ typedef struct PGPROC PGPROC; +typedef struct PGPROC_MINIMAL PGPROC_MINIMAL; typedef struct PROC_QUEUE { diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 6e798b1..a136309 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -35,8 +35,6 @@ struct XidCache { - bool overflowed; - int nxids; TransactionId xids[PGPROC_MAX_CACHED_SUBXIDS]; }; @@ -57,6 +55,8 @@ struct XidCache */ #define FP_LOCK_SLOTS_PER_BACKEND 16 +struct PGPROC_MINIMAL; + /* * Each backend has a PGPROC struct in shared memory. There is also a list of * currently-unused PGPROC structs that will be reallocated to new backends. @@ -87,14 +87,7 @@ struct PGPROC * being executed by this proc, if running; * else InvalidLocalTransactionId */ - TransactionId xid; /* id of top-level transaction currently being - * executed by this proc, if running and XID - * is assigned; else InvalidTransactionId */ - - TransactionId xmin; /* minimal running XID as it was when we were - * starting our xact, excluding LAZY VACUUM: - * vacuum must not remove tuples deleted by - * xid >= xmin ! */ + PGPROC_MINIMAL *proc_minimal; int pid; /* Backend's process ID; 0 if prepared xact */ @@ -103,10 +96,6 @@ struct PGPROC Oid databaseId; /* OID of database this backend is using */ Oid roleId; /* OID of role using this backend */ - bool inCommit; /* true if within commit critical section */ - - uint8 vacuumFlags; /* vacuum-related flags, see above */ - /* * While in hot standby mode, shows that a conflict signal has been sent * for the current transaction. Set/cleared while holding ProcArrayLock, @@ -161,6 +150,32 @@ struct PGPROC extern PGDLLIMPORT PGPROC *MyProc; +/* + * A minimal part of the PGPROC. We store these members out of the main PGPROC + * structure since they are very heavily accessed members and usually in a loop + * for all active PGPROCs. Storing them in a separate array ensures that these + * members can be very effeciently accessed with minimum cache misses. On a + * large multiprocessor system, this can show a significant performance + * improvement. + */ +struct PGPROC_MINIMAL +{ + TransactionId xid; /* id of top-level transaction currently being + * executed by this proc, if running and XID + * is assigned; else InvalidTransactionId */ + + TransactionId xmin; /* minimal running XID as it was when we were + * starting our xact, excluding LAZY VACUUM: + * vacuum must not remove tuples deleted by + * xid >= xmin ! */ + + uint8 vacuumFlags; /* vacuum-related flags, see above */ + bool overflowed; + bool inCommit; /* true if within commit critical section */ + + int nxids; +}; + /* * There is one ProcGlobal struct for the whole database cluster. @@ -169,6 +184,8 @@ typedef struct PROC_HDR { /* Array of PGPROC structures (not including dummies for prepared txns) */ PGPROC *allProcs; + /* Array of PGPROC_MINIMAL structures (not including dummies for prepared txns */ + PGPROC_MINIMAL *allProcs_Minimal; /* Length of allProcs array */ uint32 allProcCount; /* Head of list of free PGPROC structures */