diff --git a/contrib/pgstattuple/pgstatbloat.c b/contrib/pgstattuple/pgstatbloat.c
index 2dd1900..b19459a 100644
--- a/contrib/pgstattuple/pgstatbloat.c
+++ b/contrib/pgstattuple/pgstatbloat.c
@@ -193,6 +193,12 @@ pgstatbloat(PG_FUNCTION_ARGS)
 }
 
 /*
+ * Before we consider skipping a page that's marked as clean in
+ * visibility map, we must've seen at least this many clean pages.
+ */
+#define SKIP_PAGES_THRESHOLD    ((BlockNumber) 32)
+
+/*
  * This function takes an already open relation and scans its pages,
  * skipping those that have the corresponding visibility map bit set.
  * For pages we skip, we find the free space from the free space map
@@ -213,6 +219,8 @@ pgstatbloat_heap(Relation rel, FunctionCallInfo fcinfo)
 	pgstatbloat_output_type stat = {0};
 	BufferAccessStrategy bstrategy;
 	TransactionId OldestXmin;
+	BlockNumber next_not_all_visible_block;
+	bool		skipping_all_visible_blocks;
 
 	OldestXmin = GetOldestXmin(rel, true);
 	bstrategy = GetAccessStrategy(BAS_BULKREAD);
@@ -220,6 +228,24 @@ pgstatbloat_heap(Relation rel, FunctionCallInfo fcinfo)
 	scanned = 0;
 	nblocks = RelationGetNumberOfBlocks(rel);
 
+	/*
+	 * We use the visibility map to skip over SKIP_PAGES_THRESHOLD or
+	 * more contiguous all-visible pages. See the explanation in
+	 * lazy_scan_heap for the rationale.
+	 */
+
+	for (next_not_all_visible_block = 0;
+		 next_not_all_visible_block < nblocks;
+		 next_not_all_visible_block++)
+	{
+		if (!visibilitymap_test(rel, next_not_all_visible_block, &vmbuffer))
+			break;
+	}
+	if (next_not_all_visible_block >= SKIP_PAGES_THRESHOLD)
+		skipping_all_visible_blocks = true;
+	else
+		skipping_all_visible_blocks = false;
+
 	for (blkno = 0; blkno < nblocks; blkno++)
 	{
 		Buffer		buf;
@@ -230,13 +256,34 @@ pgstatbloat_heap(Relation rel, FunctionCallInfo fcinfo)
 
 		CHECK_FOR_INTERRUPTS();
 
-		/*
-		 * If the page has only visible tuples, then we can find out the
-		 * free space from the FSM and move on.
-		 */
+		if (blkno == next_not_all_visible_block)
+		{
+			/* Time to advance next_not_all_visible_block */
+			for (next_not_all_visible_block++;
+				 next_not_all_visible_block < nblocks;
+				 next_not_all_visible_block++)
+			{
+				if (!visibilitymap_test(rel, next_not_all_visible_block,
+										&vmbuffer))
+					break;
+			}
 
-		if (visibilitymap_test(rel, blkno, &vmbuffer))
+			/*
+			 * We know we can't skip the current block.  But set up
+			 * skipping_all_visible_blocks to do the right thing at the
+			 * following blocks.
+			 */
+			if (next_not_all_visible_block - blkno > SKIP_PAGES_THRESHOLD)
+				skipping_all_visible_blocks = true;
+			else
+				skipping_all_visible_blocks = false;
+		}
+		else if (skipping_all_visible_blocks)
 		{
+			/*
+			 * We can skip this all-visible page, so we just find out
+			 * the free space from the FSM.
+			 */
 			freespace = GetRecordedFreeSpace(rel, blkno);
 			stat.tuple_len += BLCKSZ - freespace;
 			stat.free_space += freespace;
