diff --git a/src/backend/access/transam/README b/src/backend/access/transam/README
index 0f61f814c0..4feedf96dd 100644
--- a/src/backend/access/transam/README
+++ b/src/backend/access/transam/README
@@ -377,7 +377,11 @@ transaction.
 
 The "subtransaction parent" (pg_subtrans) mechanism records, for each
 transaction with an XID, the TransactionId of its top-level transaction.  This
-information is stored as soon as the subtransaction is assigned an XID.
+information is stored as soon as the subtransaction is assigned an XID,
+if the owning backend knows that others may need to discover the topxid;
+please see src/backend/access/transam/subtrans.c for gory details. In practice,
+this means that very few parent xids are ever stored in pg_subtrans, an
+optimization which greatly reduces contention for that data structure.
 Top-level transactions do not have a parent, so they leave their pg_subtrans
 entries set to the default value of zero (InvalidTransactionId).
 
diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index 77d9894dab..91645be641 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -107,7 +107,20 @@ 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 we will need to
+ * set a status of TRANSACTION_STATUS_SUB_COMMITTED during
+ * TransactionIdSetTreeStatus(), which occurs during final commit.
+ * Knowing this in advance may help optimization of SubTransSetParent().
+ */
+bool
+TransactionIdsAreOnSameXactPage(TransactionId topxid, TransactionId subxid)
+{
+	int	toppageno = TransactionIdToPage(topxid);
+	int	subpageno = TransactionIdToPage(subxid);
 
+	return (toppageno == subpageno);
+}
 
 /*
  * TransactionIdSetTreeStatus
@@ -133,7 +146,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/subtrans.c b/src/backend/access/transam/subtrans.c
index ac92588b40..3a1ca9b80f 100644
--- a/src/backend/access/transam/subtrans.c
+++ b/src/backend/access/transam/subtrans.c
@@ -8,7 +8,36 @@
  * nested transactions implementation.
  *
  * A top-level xact's entry in subtrans is always InvalidTransactionId.
- * A subxact's entry in subtrans always points direct to the topxid.
+ * Other aspects of the contents of SUBTRANS change in PG16:
+ *
+ * 1. If the parent xid is stored, we always point direct to topxid
+ *    and the only call to access this if SubTransGetTopmostTransaction();
+ *    SubTransGetParent() is no longer exported for other modules.
+ * 2. A transaction will store the parent xid only when needed, so in
+ *    many cases the value will remain as InvalidTransactionId.
+ *    This is correct because SubTransGetTopmostTransaction() has a short
+ *    list of potential callers:
+ *
+ * a) When there is no room in the subxid cache, we make calls from
+ *    XidInMVCCSnapshot() only when the xid is not found in snapshot subxip
+ *    which has also been modified in PG16 to allow this to work
+ *    and TransactionIdIsInProgress() when not in recovery and the xid is
+ *    not found in the subxid cache in PGPROC.
+ * b) HeapCheckForSerializableConflictOut() calls if in a serializable xact
+ *    and we are looking for another xact that has caused SIRead locks,
+ *    i.e. another serializable transaction.
+ * c) TransactionIdDidCommit()/TransactionIdDidAbort() called if we see the
+ *    transient state of subcommitted in clog.
+ * d) XactLockTableWait() calls only in Hot Standby mode.
+ *
+ * Since a), b) and c) are known in advance by the originating xact, we can
+ * often avoid the need to call SubTransSetParent() since we know that no
+ * caller needs the data in subtrans to be correct except in the above cases.
+ * If this changes, remember to modify AssignTransactionId().
+ *
+ * The above details allow PG16 to significantly reduce the contention
+ * around subtrans seen when in subtransaction overflow state, especially
+ * with long running transactions.
  *
  * This code is based on xact.c, but the robustness requirements
  * are completely different from pg_xact, because we only need to remember
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index be15ac25f3..e45f151c7f 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -701,12 +701,52 @@ AssignTransactionId(TransactionState s)
 		TransactionId topxid = GetTopTransactionId();
 
 		/*
-		 * Insert entries into subtrans for this xid, noting that the entry
-		 * points directly to the topxid, not the immediate parent. This is
-		 * so it is faster in a long chain of subxids, because the
-		 * algorithm is then O(1), no matter how many subxids are assigned.
+		 * Subtrans entries are only required in specific circumstances:
+		 *
+		 * a) When there's no room in the PG_PROC subxid cache, as mentioned above.
+		 *    i) During XidInMVCCSnapshot() we may need to call
+		 *    GetTopmostTransactionIdFromProcArray() if the snapshot overflowed.
+		 *
+		 *	  ii) When TransactionIdIsInProgress() finds subxid cache overflowed
+		 *	  we may need to call GetTopmostTransactionIdFromProcArray()
+		 *
+		 *    iii) 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.
+		 *
+		 * b) When IsolationIsSerializable() we sometimes need to access topxid
+		 *    during HeapCheckForSerializableConflictOut().
+		 *    This occurs only when SERIALIZABLE is requested by app user.
+		 *
+		 * c) When TransactionIdSetTreeStatus() will use status SUB_COMMITTED,
+		 *    which then requires TransactionIdDidCommit() or TransactionIdDidAbort()
+		 *    to consult subtrans to find parent. 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,
+		 *    depending upon how far apart the subxacts assign subxids.
 		 */
-		SubTransSetParent(subxid, topxid);
+		if (MyProc->subxidStatus.overflowed ||
+			IsolationIsSerializable() ||
+			!TransactionIdsAreOnSameXactPage(topxid, 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, because the
+			 * algorithm is then O(1), no matter how many subxids are assigned.
+			 * (2) so that we don't need to set subxids for unregistered parents.
+			 * Previously when we set the parent to be the immediate parent,
+			 * we then had to set the parent in all cases to maintain the chain
+			 * of values to reach the topxid. If all subxids point to topxid,
+			 * then they are independent of each other and we can skip some.
+			 */
+			SubTransSetParent(subxid, topxid);
+		}
 	}
 
 	/*
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
