From 5e5a089bc14eb29df8b16da8ebec38344f7fad1c Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Mon, 21 Apr 2025 22:40:57 -0700
Subject: [PATCH v3 2/4] tidstore.c: introduce TidStoreIsMemberMulti.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch-through:
---
 src/backend/access/common/tidstore.c          | 62 +++++++++++++------
 src/backend/commands/vacuum.c                 |  3 +-
 src/include/access/tidstore.h                 |  3 +-
 .../modules/test_tidstore/test_tidstore.c     | 11 +++-
 4 files changed, 57 insertions(+), 22 deletions(-)

diff --git a/src/backend/access/common/tidstore.c b/src/backend/access/common/tidstore.c
index 5bd75fb499c..270ef260630 100644
--- a/src/backend/access/common/tidstore.c
+++ b/src/backend/access/common/tidstore.c
@@ -416,20 +416,11 @@ TidStoreSetBlockOffsets(TidStore *ts, BlockNumber blkno, OffsetNumber *offsets,
 		local_ts_set(ts->tree.local, blkno, page);
 }
 
-/* Return true if the given TID is present in the TidStore */
-bool
-TidStoreIsMember(TidStore *ts, ItemPointer tid)
+static pg_attribute_always_inline int
+tidstore_is_member_page(BlocktableEntry *page, OffsetNumber off)
 {
 	int			wordnum;
 	int			bitnum;
-	BlocktableEntry *page;
-	BlockNumber blk = ItemPointerGetBlockNumber(tid);
-	OffsetNumber off = ItemPointerGetOffsetNumber(tid);
-
-	if (TidStoreIsShared(ts))
-		page = shared_ts_find(ts->tree.shared, blk);
-	else
-		page = local_ts_find(ts->tree.local, blk);
 
 	/* no entry for the blk */
 	if (page == NULL)
@@ -443,19 +434,54 @@ TidStoreIsMember(TidStore *ts, ItemPointer tid)
 			if (page->header.full_offsets[i] == off)
 				return true;
 		}
+
 		return false;
 	}
-	else
+
+	wordnum = WORDNUM(off);
+	bitnum = BITNUM(off);
+
+	/* no bitmap for the off */
+	if (wordnum >= page->header.nwords)
+		return false;
+
+	return (page->words[wordnum] & ((bitmapword) 1 << bitnum)) != 0;
+}
+
+/*
+ * Check if the given TIDs are present in the TidStore. Return the number
+ * of TIDs found in the TidStore. *ismember must have enough space for
+ * up to 'ntids' elements.
+ */
+int
+TidStoreIsMemberMulti(TidStore *ts, ItemPointer tids, int ntids, bool *ismembers)
+{
+	BlocktableEntry *page = NULL;
+	BlockNumber last_blk = InvalidBlockNumber;
+	int			nmembers = 0;
+
+	for (int i = 0; i < ntids; i++)
 	{
-		wordnum = WORDNUM(off);
-		bitnum = BITNUM(off);
+		ItemPointer tid = &(tids[i]);
+		BlockNumber blk = ItemPointerGetBlockNumber(tid);
+		OffsetNumber off = ItemPointerGetOffsetNumber(tid);
 
-		/* no bitmap for the off */
-		if (wordnum >= page->header.nwords)
-			return false;
+		if (blk != last_blk)
+		{
+			if (TidStoreIsShared(ts))
+				page = shared_ts_find(ts->tree.shared, blk);
+			else
+				page = local_ts_find(ts->tree.local, blk);
+		}
 
-		return (page->words[wordnum] & ((bitmapword) 1 << bitnum)) != 0;
+		ismembers[i] = tidstore_is_member_page(page, off);
+		if (ismembers[i])
+			nmembers++;
+
+		last_blk = blk;
 	}
+
+	return nmembers;
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 33a33bf6b1c..e47b61772b1 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2658,6 +2658,7 @@ static bool
 vac_tid_reaped(ItemPointer itemptr, void *state)
 {
 	TidStore   *dead_items = (TidStore *) state;
+	bool		isdead;
 
-	return TidStoreIsMember(dead_items, itemptr);
+	return TidStoreIsMemberMulti(dead_items, itemptr, 1, &isdead) > 0;
 }
diff --git a/src/include/access/tidstore.h b/src/include/access/tidstore.h
index 041091df278..b6137e78780 100644
--- a/src/include/access/tidstore.h
+++ b/src/include/access/tidstore.h
@@ -40,7 +40,8 @@ extern void TidStoreUnlock(TidStore *ts);
 extern void TidStoreDestroy(TidStore *ts);
 extern void TidStoreSetBlockOffsets(TidStore *ts, BlockNumber blkno, OffsetNumber *offsets,
 									int num_offsets);
-extern bool TidStoreIsMember(TidStore *ts, ItemPointer tid);
+extern int	TidStoreIsMemberMulti(TidStore *ts, ItemPointer tids, int ntids,
+								  bool *ismembers);
 extern TidStoreIter *TidStoreBeginIterate(TidStore *ts);
 extern TidStoreIterResult *TidStoreIterateNext(TidStoreIter *iter);
 extern int	TidStoreGetBlockOffsets(TidStoreIterResult *result,
diff --git a/src/test/modules/test_tidstore/test_tidstore.c b/src/test/modules/test_tidstore/test_tidstore.c
index eb16e0fbfa6..277d437e237 100644
--- a/src/test/modules/test_tidstore/test_tidstore.c
+++ b/src/test/modules/test_tidstore/test_tidstore.c
@@ -222,16 +222,22 @@ check_set_block_offsets(PG_FUNCTION_ARGS)
 	TidStoreIterResult *iter_result;
 	int			num_iter_tids = 0;
 	int			num_lookup_tids = 0;
+	bool	   *ismembers;
 	BlockNumber prevblkno = 0;
 
 	check_tidstore_available();
 
 	/* lookup each member in the verification array */
+	ismembers = palloc(sizeof(bool) * items.num_tids);
+	TidStoreIsMemberMulti(tidstore, items.insert_tids, items.num_tids, ismembers);
 	for (int i = 0; i < items.num_tids; i++)
-		if (!TidStoreIsMember(tidstore, &items.insert_tids[i]))
+	{
+		if (!ismembers[i])
 			elog(ERROR, "missing TID with block %u, offset %u",
 				 ItemPointerGetBlockNumber(&items.insert_tids[i]),
 				 ItemPointerGetOffsetNumber(&items.insert_tids[i]));
+	}
+	pfree(ismembers);
 
 	/*
 	 * Lookup all possible TIDs for each distinct block in the verification
@@ -248,11 +254,12 @@ check_set_block_offsets(PG_FUNCTION_ARGS)
 		for (OffsetNumber offset = FirstOffsetNumber; offset < MaxOffsetNumber; offset++)
 		{
 			ItemPointerData tid;
+			bool		ismember;
 
 			ItemPointerSet(&tid, blkno, offset);
 
 			TidStoreLockShare(tidstore);
-			if (TidStoreIsMember(tidstore, &tid))
+			if (TidStoreIsMemberMulti(tidstore, &tid, 1, &ismember) > 0)
 				ItemPointerSet(&items.lookup_tids[num_lookup_tids++], blkno, offset);
 			TidStoreUnlock(tidstore);
 		}
-- 
2.43.5

