diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index 3d9088a704..d690774f33 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -107,7 +107,18 @@ static bool TransactionGroupUpdateXidStatus(TransactionId xid,
 static void TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
 											   TransactionId *subxids, XidStatus status,
 											   XLogRecPtr lsn, int pageno);
+/*
+ * Run locally by a backend to establish whether or not it needs to call
+ * SubTransSetParent for subxid.
+ */
+bool
+TransactionIdsAreOnSameXactPage(TransactionId topxid, TransactionId subxid)
+{
+	int	toppageno = TransactionIdToPage(topxid);
+	int	subpageno = TransactionIdToPage(subxid);
 
+	return (toppageno == subpageno);
+}
 
 /*
  * TransactionIdSetTreeStatus
@@ -133,7 +144,7 @@ static void TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
  * only once, and the status will be set to committed directly.  Otherwise
  * we must
  *	 1. set sub-committed all subxids that are not on the same page as the
- *		main xid
+ *		main xid (see TransactionIdsAreOnSameXactPage())
  *	 2. atomically set committed the main xid and the subxids on the same page
  *	 3. go over the first bunch again and set them committed
  * Note that as far as concurrent checkers are concerned, main transaction
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 50f092d7eb..5577e5b8df 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -693,8 +693,51 @@ AssignTransactionId(TransactionState s)
 		XactTopFullTransactionId = s->fullTransactionId;
 
 	if (isSubXact)
-		SubTransSetParent(XidFromFullTransactionId(s->fullTransactionId),
-						  XidFromFullTransactionId(s->parent->fullTransactionId));
+	{
+		TransactionId subxid = XidFromFullTransactionId(s->fullTransactionId);
+
+		/*
+		 * Subtrans entries are only required in specific circumstances:
+		 *
+		 * 1. When there's no room in PG_PROC, as mentioned above.
+		 *    During XactLockTableWait() we sometimes need to know the topxid.
+		 *    If there is room in PG_PROC we can get a subxid's topxid direct
+		 *    from the procarray if the topxid is still running, using
+		 *    GetTopmostTransactionIdFromProcArray(). So we only ever need to
+		 *    call SubTransGetTopMostTransaction() if that xact overflowed;
+		 *    since that is our current transaction, we know whether or not to
+		 *    log the xid for future use.
+		 *    This occurs only when large number of subxids are requested by
+		 *    app user.
+		 *
+		 * 2. When IsolationIsSerializable() we sometimes need to access topxid
+		 *    This occurs only when SERIALIZABLE is requested by app user.
+		 *
+		 * 3. When TransactionIdSetStatus will use a status of SUB_COMMITTED,
+		 *    which then requires us to consult subtrans to find parent, which
+		 *    is needed to avoid race condition. In this case we ask Clog/Xact
+		 *    module if TransactionIdsAreOnSameXactPage(). Since we start a new
+		 *    clog page every 32000 xids, this is usually <<1% of subxids.
+		 */
+		if (MyProc->subxidStatus.overflowed ||
+			IsolationIsSerializable() ||
+			!TransactionIdsAreOnSameXactPage(GetTopTransactionId(), subxid))
+		{
+			/*
+			 * Insert entries into subtrans for this xid, noting that the entry
+			 * points directly to the topxid, not the immediate parent. This is
+			 * done for two reasons, (1) so it is faster in a long chain of subxids
+			 * (2) so that we don't need to set subxids for unregistered parents.
+			 * This has the downside that anyone waiting for a lock on aborted
+			 * subtransactions would not be released immediately; that may or
+			 * may not be an acceptable compromise. If not acceptable, this
+			 * simple call needs to be replaced with a loop to register the
+			 * parent for the current subxid stack, so we can walk back up it to
+			 * the topxid.
+			 */
+			SubTransSetParent(subxid, GetTopTransactionId());
+		}
+	}
 
 	/*
 	 * If it's a top-level transaction, the predicate locking system needs to
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a9ad40e935..f7c1061a6e 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -261,6 +261,13 @@ static ProcArrayStruct *procArray;
 
 static PGPROC *allProcs;
 
+/*
+ * Remember the last call to TransactionIdIsInProgress() to avoid need to call
+ * SubTransGetTopMostTransaction() when the subxid is present in the procarray.
+ */
+static TransactionId LastCallXidIsInProgressSubXid = InvalidTransactionId;
+static TransactionId LastCallXidIsInProgressParentXid = InvalidTransactionId;
+
 /*
  * Cache to reduce overhead of repeated calls to TransactionIdIsInProgress()
  */
@@ -1440,6 +1447,8 @@ TransactionIdIsInProgress(TransactionId xid)
 	other_xids = ProcGlobal->xids;
 	other_subxidstates = ProcGlobal->subxidStates;
 
+	LastCallXidIsInProgressSubXid = LastCallXidIsInProgressParentXid = InvalidTransactionId;
+
 	LWLockAcquire(ProcArrayLock, LW_SHARED);
 
 	/*
@@ -1508,6 +1517,15 @@ TransactionIdIsInProgress(TransactionId xid)
 			{
 				LWLockRelease(ProcArrayLock);
 				xc_by_child_xid_inc();
+
+				/*
+				 * Remember the parent xid, for use during XactLockTableWait().
+				 * We do this because it is cheaper than looking up pg_subtrans,
+				 * and also allows us to reduce calls to subtrans.
+				 */
+				LastCallXidIsInProgressSubXid = xid;
+				LastCallXidIsInProgressParentXid = pxid;
+
 				return true;
 			}
 		}
@@ -1591,7 +1609,11 @@ TransactionIdIsInProgress(TransactionId xid)
 		for (int i = 0; i < nxids; i++)
 		{
 			if (TransactionIdEquals(xids[i], topxid))
+			{
+				LastCallXidIsInProgressSubXid = xid;
+				LastCallXidIsInProgressParentXid = topxid;
 				return true;
+			}
 		}
 	}
 
@@ -1599,6 +1621,28 @@ TransactionIdIsInProgress(TransactionId xid)
 	return false;
 }
 
+/*
+ * Allow the topmost xid to be accessed from the last call to
+ * TransactionIdIsInProgress(). Specifically designed for use in
+ * XactLockTableWait().
+ */
+bool
+GetTopmostTransactionIdFromProcArray(TransactionId xid, TransactionId *pxid)
+{
+	bool found = false;
+
+	Assert(TransactionIdIsNormal(xid));
+
+	if (LastCallXidIsInProgressSubXid == xid)
+	{
+		Assert(TransactionIdIsNormal(*pxid));
+		*pxid = LastCallXidIsInProgressParentXid;
+		found = true;
+	}
+
+	return found;
+}
+
 /*
  * TransactionIdIsActive -- is xid the top-level XID of an active backend?
  *
diff --git a/src/backend/storage/lmgr/lmgr.c b/src/backend/storage/lmgr/lmgr.c
index 1543da6162..b13a8d20fd 100644
--- a/src/backend/storage/lmgr/lmgr.c
+++ b/src/backend/storage/lmgr/lmgr.c
@@ -694,6 +694,8 @@ XactLockTableWait(TransactionId xid, Relation rel, ItemPointer ctid,
 
 	for (;;)
 	{
+		TransactionId pxid = InvalidTransactionId;
+
 		Assert(TransactionIdIsValid(xid));
 		Assert(!TransactionIdEquals(xid, GetTopTransactionIdIfAny()));
 
@@ -703,6 +705,13 @@ XactLockTableWait(TransactionId xid, Relation rel, ItemPointer ctid,
 
 		LockRelease(&tag, ShareLock, false);
 
+		/*
+		 * If a transaction has no lock, it might be a top-level transaction,
+		 * in which case the procarray will show it as not in progress.
+		 *
+		 * If a transaction is a subtransaction, then it could have committed
+		 * or aborted, yet the top-level transaction may still be in progress.
+		 */
 		if (!TransactionIdIsInProgress(xid))
 			break;
 
@@ -724,7 +733,17 @@ XactLockTableWait(TransactionId xid, Relation rel, ItemPointer ctid,
 		if (!first)
 			pg_usleep(1000L);
 		first = false;
-		xid = SubTransGetTopmostTransaction(xid);
+
+		/*
+		 * In most cases, we can get the parent xid from our prior call to
+		 * TransactionIdIsInProgress(), except in hot standby. If not, we have
+		 * to ask subtrans for the parent.
+		 */
+		if (GetTopmostTransactionIdFromProcArray(xid, &pxid) &&
+			TransactionIdIsValid(pxid))
+			xid = pxid;
+		else
+			xid = SubTransGetTopmostTransaction(xid);
 	}
 
 	if (oper != XLTW_None)
@@ -745,6 +764,8 @@ ConditionalXactLockTableWait(TransactionId xid)
 
 	for (;;)
 	{
+		TransactionId pxid = InvalidTransactionId;
+
 		Assert(TransactionIdIsValid(xid));
 		Assert(!TransactionIdEquals(xid, GetTopTransactionIdIfAny()));
 
@@ -762,7 +783,13 @@ ConditionalXactLockTableWait(TransactionId xid)
 		if (!first)
 			pg_usleep(1000L);
 		first = false;
-		xid = SubTransGetTopmostTransaction(xid);
+
+		/* See XactLockTableWait about this case */
+		if (GetTopmostTransactionIdFromProcArray(xid, &pxid) &&
+			TransactionIdIsValid(pxid))
+			xid = pxid;
+		else
+			xid = SubTransGetTopmostTransaction(xid);
 	}
 
 	return true;
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index 775471d2a7..f404db552f 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -301,6 +301,9 @@ extern void AssertTransactionIdInAllowableRange(TransactionId xid);
 #define AssertTransactionIdInAllowableRange(xid) ((void)true)
 #endif
 
+/* in transam/clog.c */
+extern bool TransactionIdsAreOnSameXactPage(TransactionId topxid, TransactionId subxid);
+
 /*
  * Some frontend programs include this header.  For compilers that emit static
  * inline functions even when they're unused, that leads to unsatisfied
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index 1b2cfac5ad..d7ad1da6a8 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -52,6 +52,8 @@ extern bool ProcArrayInstallRestoredXmin(TransactionId xmin, PGPROC *proc);
 extern RunningTransactions GetRunningTransactionData(void);
 
 extern bool TransactionIdIsInProgress(TransactionId xid);
+extern bool GetTopmostTransactionIdFromProcArray(TransactionId xid, TransactionId *pxid);
+
 extern bool TransactionIdIsActive(TransactionId xid);
 extern TransactionId GetOldestNonRemovableTransactionId(Relation rel);
 extern TransactionId GetOldestTransactionIdConsideredRunning(void);
