From d4fe1827f338e38ed5dbc2042ab199fb09954fbd Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Wed, 1 Oct 2025 18:04:02 -0400
Subject: [PATCH v4 2/2] Make some minor performance improvements in psql's
 line-counting.

Arrange to not run count_table_lines at all unless we will use
its result, and teach it to quit early as soon as it's proven
that the output is long enough to require use of the pager.
When dealing with large tables this should save a noticeable
amount of time, since pg_wcssize() isn't exactly cheap.

In passing, fix print_aligned_text to avoid using repeated
"i % col_count" when it could just make the value of i wrap
around.  In a quick check with the version of gcc I'm using,
the compiler is smart enough to recognize the common subexpressions
and issue only one integer divide per loop, but it's not smart enough
to avoid the integer divide altogether.  In any case, this coding
was randomly unlike the way it's done in count_table_lines.

Author: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/2dd2430f-dd20-4c89-97fd-242616a3d768@ewie.name
---
 src/fe_utils/print.c | 97 +++++++++++++++++++++++++++++++++-----------
 1 file changed, 73 insertions(+), 24 deletions(-)

diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 71bc14d499b..d8c8d7d2a3b 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -266,13 +266,18 @@ static const unicodeStyleFormat unicode_style = {
 
 /* Local functions */
 static int	strlen_max_width(unsigned char *str, int *target_width, int encoding);
+static FILE *PageOutputInternal(int lines, const printTableOpt *topt,
+								const printTableContent *cont,
+								const unsigned int *width_wrap,
+								bool expanded);
 static void IsPagerNeeded(const printTableContent *cont,
 						  const unsigned int *width_wrap,
 						  bool expanded,
 						  FILE **fout, bool *is_pager);
 static int	count_table_lines(const printTableContent *cont,
 							  const unsigned int *width_wrap,
-							  bool expanded);
+							  bool expanded,
+							  int threshold);
 
 static void print_aligned_vertical(const printTableContent *cont,
 								   FILE *fout, bool is_pager);
@@ -730,7 +735,7 @@ print_aligned_text(const printTableContent *cont, FILE *fout, bool is_pager)
 	}
 
 	/* scan all cells, find maximum width, compute cell_count */
-	for (i = 0, ptr = cont->cells; *ptr; ptr++, i++, cell_count++)
+	for (i = 0, ptr = cont->cells; *ptr; ptr++, cell_count++)
 	{
 		int			width,
 					nl_lines,
@@ -739,14 +744,18 @@ print_aligned_text(const printTableContent *cont, FILE *fout, bool is_pager)
 		pg_wcssize((const unsigned char *) *ptr, strlen(*ptr), encoding,
 				   &width, &nl_lines, &bytes_required);
 
-		if (width > max_width[i % col_count])
-			max_width[i % col_count] = width;
-		if (nl_lines > max_nl_lines[i % col_count])
-			max_nl_lines[i % col_count] = nl_lines;
-		if (bytes_required > max_bytes[i % col_count])
-			max_bytes[i % col_count] = bytes_required;
+		if (width > max_width[i])
+			max_width[i] = width;
+		if (nl_lines > max_nl_lines[i])
+			max_nl_lines[i] = nl_lines;
+		if (bytes_required > max_bytes[i])
+			max_bytes[i] = bytes_required;
+
+		width_average[i] += width;
 
-		width_average[i % col_count] += width;
+		/* i is the current column number: increment with wrap */
+		if (++i >= col_count)
+			i = 0;
 	}
 
 	/* If we have rows, compute average */
@@ -3046,12 +3055,31 @@ set_sigpipe_trap_state(bool ignore)
 /*
  * PageOutput
  *
- * Tests if pager is needed and returns appropriate FILE pointer.
+ * Tests if pager is needed and returns appropriate FILE pointer
+ * (either a pipe, or stdout if we don't need the pager).
+ *
+ * lines: number of lines that will be printed
+ * topt: print formatting options
  *
  * If the topt argument is NULL no pager is used.
  */
 FILE *
 PageOutput(int lines, const printTableOpt *topt)
+{
+	return PageOutputInternal(lines, topt, NULL, NULL, false);
+}
+
+/*
+ * Private version that allows for line-counting to be avoided when
+ * not needed.  If "cont" is not null then the input value of "lines"
+ * is ignored and we count lines based on cont + width_wrap + expanded
+ * (see count_table_lines).
+ */
+static FILE *
+PageOutputInternal(int lines, const printTableOpt *topt,
+				   const printTableContent *cont,
+				   const unsigned int *width_wrap,
+				   bool expanded)
 {
 	/* check whether we need / can / are supposed to use pager */
 	if (topt && topt->pager && isatty(fileno(stdin)) && isatty(fileno(stdout)))
@@ -3059,15 +3087,29 @@ PageOutput(int lines, const printTableOpt *topt)
 #ifdef TIOCGWINSZ
 		unsigned short int pager = topt->pager;
 		int			min_lines = topt->pager_min_lines;
-		int			result;
-		struct winsize screen_size;
 
-		result = ioctl(fileno(stdout), TIOCGWINSZ, &screen_size);
+		if (pager == 1)
+		{
+			int			result;
+			struct winsize screen_size;
 
-		/* >= accounts for a one-line prompt */
-		if (result == -1
-			|| (lines >= screen_size.ws_row && lines >= min_lines)
-			|| pager > 1)
+			result = ioctl(fileno(stdout), TIOCGWINSZ, &screen_size);
+			if (result < 0)
+				pager = 2;		/* force use of pager */
+			else
+			{
+				int			threshold = Max(screen_size.ws_row, min_lines);
+
+				if (cont)		/* caller wants us to calculate lines */
+					lines = count_table_lines(cont, width_wrap, expanded,
+											  threshold);
+				/* >= accounts for a one-line prompt */
+				if (lines >= threshold)
+					pager = 2;
+			}
+		}
+
+		if (pager > 1)
 #endif
 		{
 			const char *pagerprog;
@@ -3379,11 +3421,7 @@ IsPagerNeeded(const printTableContent *cont, const unsigned int *width_wrap,
 {
 	if (*fout == stdout)
 	{
-		int			lines;
-
-		lines = count_table_lines(cont, width_wrap, expanded);
-
-		*fout = PageOutput(lines, cont->opt);
+		*fout = PageOutputInternal(0, cont->opt, cont, width_wrap, expanded);
 		*is_pager = (*fout != stdout);
 	}
 	else
@@ -3396,13 +3434,18 @@ IsPagerNeeded(const printTableContent *cont, const unsigned int *width_wrap,
  * cont: table data to be printed
  * width_wrap[]: per-column maximum width, or NULL if caller will not wrap
  * expanded: expanded mode?
+ * threshold: we can stop counting once we pass threshold
  *
  * We currently handle only regular and expanded modes here.
+ *
+ * The value of the threshold parameter is that when the table is very
+ * long, we'll typically be able to stop scanning after not many rows.
  */
 static int
 count_table_lines(const printTableContent *cont,
 				  const unsigned int *width_wrap,
-				  bool expanded)
+				  bool expanded,
+				  int threshold)
 {
 	int		   *header_height;
 	int			lines,
@@ -3468,11 +3511,14 @@ count_table_lines(const printTableContent *cont,
 				lines += max_lines;
 				max_lines = 0;
 			}
+			/* Stop scanning table body once we pass threshold */
+			if (lines > threshold)
+				break;
 		}
 	}
 
 	/* Account for header and footer decoration */
-	if (!cont->opt->tuples_only)
+	if (!cont->opt->tuples_only && lines <= threshold)
 	{
 		if (cont->title)
 		{
@@ -3500,6 +3546,9 @@ count_table_lines(const printTableContent *cont,
 			pg_wcssize((const unsigned char *) f->data, strlen(f->data),
 					   encoding, NULL, &nl_lines, NULL);
 			lines += nl_lines;
+			/* Stop scanning footers once we pass threshold */
+			if (lines > threshold)
+				break;
 		}
 	}
 
-- 
2.43.7

