commit 5b781cbd1635df1640573955f63f3a9b52b29d7f
Author: Tom Lane <tgl@sss.pgh.pa.us>
Date:   Tue Sep 13 12:03:01 2022 -0400

    Replace sorted array of GUC variables with a hash table.
    
    Get rid of bsearch() in favor of hashed lookup.  The main
    advantage is that it becomes far cheaper to add new GUCs,
    since we needn't re-sort the pointer array.  Adding N new
    GUCs had been O(N^2 log N), but now it's closer to O(N).
    
    Also, rationalize a couple of not-very-well-thought-out APIs.
    GetConfigOptionByNum() is outright dangerous in a world where
    the set of GUCs isn't fairly static; not to mention that
    somebody had whacked its output around to the point where
    it was useless to anyone except show_all_settings().
    Stop exporting that, and set things up so that the result
    of get_guc_variables() can be trusted even if more GUCs
    get added while one is still consulting it.

diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 985979f140..4d25b85301 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -36,6 +36,7 @@
 #include "guc_internal.h"
 #include "libpq/pqformat.h"
 #include "parser/scansup.h"
+#include "port/pg_bitutils.h"
 #include "storage/fd.h"
 #include "storage/lwlock.h"
 #include "storage/shmem.h"
@@ -192,16 +193,17 @@ static const char *const map_old_guc_names[] = {
 static MemoryContext GUCMemoryContext;
 
 /*
- * Actual lookup of variables is done through this single, sorted array.
+ * We use a dynahash table to look up GUCs by name, or to iterate through
+ * all the GUCs.  The gucname field is redundant with gucvar->name, but
+ * dynahash makes it too painful to not store the hash key separately.
  */
-static struct config_generic **guc_variables;
-
-/* Current number of variables contained in the vector */
-static int	num_guc_variables;
-
-/* Vector capacity */
-static int	size_guc_variables;
+typedef struct
+{
+	const char *gucname;		/* hash key */
+	struct config_generic *gucvar;	/* -> GUC's defining structure */
+} GUCHashEntry;
 
+static HTAB *guc_hashtab;		/* entries are GUCHashEntrys */
 
 static bool guc_dirty;			/* true if need to do commit/abort work */
 
@@ -213,6 +215,8 @@ static int	GUCNestLevel = 0;	/* 1 when in main transaction */
 
 
 static int	guc_var_compare(const void *a, const void *b);
+static uint32 guc_name_hash(const void *key, Size keysize);
+static int	guc_name_match(const void *key1, const void *key2, Size keysize);
 static void InitializeGUCOptionsFromEnvironment(void);
 static void InitializeOneGUCOption(struct config_generic *gconf);
 static void pg_timezone_abbrev_initialize(void);
@@ -262,7 +266,8 @@ ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
 	ConfigVariable *item,
 			   *head,
 			   *tail;
-	int			i;
+	HASH_SEQ_STATUS status;
+	GUCHashEntry *hentry;
 
 	/* Parse the main config file into a list of option names and values */
 	ConfFileWithError = ConfigFileName;
@@ -337,9 +342,10 @@ ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
 	 * need this so that we can tell below which ones have been removed from
 	 * the file since we last processed it.
 	 */
-	for (i = 0; i < num_guc_variables; i++)
+	hash_seq_init(&status, guc_hashtab);
+	while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
 	{
-		struct config_generic *gconf = guc_variables[i];
+		struct config_generic *gconf = hentry->gucvar;
 
 		gconf->status &= ~GUC_IS_IN_FILE;
 	}
@@ -423,9 +429,10 @@ ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
 	 * boot-time defaults.  If such a variable can't be changed after startup,
 	 * report that and continue.
 	 */
-	for (i = 0; i < num_guc_variables; i++)
+	hash_seq_init(&status, guc_hashtab);
+	while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
 	{
-		struct config_generic *gconf = guc_variables[i];
+		struct config_generic *gconf = hentry->gucvar;
 		GucStack   *stack;
 
 		if (gconf->reset_source != PGC_S_FILE ||
@@ -825,17 +832,38 @@ discard_stack_value(struct config_generic *gconf, config_var_value *val)
 
 
 /*
- * Fetch the sorted array pointer (exported for help_config.c's use ONLY)
+ * Fetch a palloc'd, sorted array of GUC struct pointers
+ *
+ * The array length is returned into *num_vars.
  */
 struct config_generic **
-get_guc_variables(void)
+get_guc_variables(int *num_vars)
 {
-	return guc_variables;
+	struct config_generic **result;
+	HASH_SEQ_STATUS status;
+	GUCHashEntry *hentry;
+	int			i;
+
+	*num_vars = hash_get_num_entries(guc_hashtab);
+	result = palloc(sizeof(struct config_generic *) * *num_vars);
+
+	/* Extract pointers from the hash table */
+	i = 0;
+	hash_seq_init(&status, guc_hashtab);
+	while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
+		result[i++] = hentry->gucvar;
+	Assert(i == *num_vars);
+
+	/* Sort by name */
+	qsort(result, *num_vars,
+		  sizeof(struct config_generic *), guc_var_compare);
+
+	return result;
 }
 
 
 /*
- * Build the sorted array.  This is split out so that help_config.c can
+ * Build the GUC hash table.  This is split out so that help_config.c can
  * extract all the variables without running all of InitializeGUCOptions.
  * It's not meant for use anyplace else.
  */
@@ -844,7 +872,9 @@ build_guc_variables(void)
 {
 	int			size_vars;
 	int			num_vars = 0;
-	struct config_generic **guc_vars;
+	HASHCTL		hash_ctl;
+	GUCHashEntry *hentry;
+	bool		found;
 	int			i;
 
 	/*
@@ -900,74 +930,106 @@ build_guc_variables(void)
 	}
 
 	/*
-	 * Create table with 20% slack
+	 * Create hash table with 20% slack
 	 */
 	size_vars = num_vars + num_vars / 4;
 
-	guc_vars = (struct config_generic **)
-		guc_malloc(FATAL, size_vars * sizeof(struct config_generic *));
-
-	num_vars = 0;
+	hash_ctl.keysize = sizeof(char *);
+	hash_ctl.entrysize = sizeof(GUCHashEntry);
+	hash_ctl.hash = guc_name_hash;
+	hash_ctl.match = guc_name_match;
+	hash_ctl.hcxt = GUCMemoryContext;
+	guc_hashtab = hash_create("GUC hash table",
+							  size_vars,
+							  &hash_ctl,
+							  HASH_ELEM | HASH_FUNCTION | HASH_COMPARE | HASH_CONTEXT);
 
 	for (i = 0; ConfigureNamesBool[i].gen.name; i++)
-		guc_vars[num_vars++] = &ConfigureNamesBool[i].gen;
+	{
+		struct config_generic *gucvar = &ConfigureNamesBool[i].gen;
+
+		hentry = (GUCHashEntry *) hash_search(guc_hashtab,
+											  &gucvar->name,
+											  HASH_ENTER,
+											  &found);
+		Assert(!found);
+		hentry->gucvar = gucvar;
+	}
 
 	for (i = 0; ConfigureNamesInt[i].gen.name; i++)
-		guc_vars[num_vars++] = &ConfigureNamesInt[i].gen;
+	{
+		struct config_generic *gucvar = &ConfigureNamesInt[i].gen;
+
+		hentry = (GUCHashEntry *) hash_search(guc_hashtab,
+											  &gucvar->name,
+											  HASH_ENTER,
+											  &found);
+		Assert(!found);
+		hentry->gucvar = gucvar;
+	}
 
 	for (i = 0; ConfigureNamesReal[i].gen.name; i++)
-		guc_vars[num_vars++] = &ConfigureNamesReal[i].gen;
+	{
+		struct config_generic *gucvar = &ConfigureNamesReal[i].gen;
+
+		hentry = (GUCHashEntry *) hash_search(guc_hashtab,
+											  &gucvar->name,
+											  HASH_ENTER,
+											  &found);
+		Assert(!found);
+		hentry->gucvar = gucvar;
+	}
 
 	for (i = 0; ConfigureNamesString[i].gen.name; i++)
-		guc_vars[num_vars++] = &ConfigureNamesString[i].gen;
+	{
+		struct config_generic *gucvar = &ConfigureNamesString[i].gen;
+
+		hentry = (GUCHashEntry *) hash_search(guc_hashtab,
+											  &gucvar->name,
+											  HASH_ENTER,
+											  &found);
+		Assert(!found);
+		hentry->gucvar = gucvar;
+	}
 
 	for (i = 0; ConfigureNamesEnum[i].gen.name; i++)
-		guc_vars[num_vars++] = &ConfigureNamesEnum[i].gen;
+	{
+		struct config_generic *gucvar = &ConfigureNamesEnum[i].gen;
+
+		hentry = (GUCHashEntry *) hash_search(guc_hashtab,
+											  &gucvar->name,
+											  HASH_ENTER,
+											  &found);
+		Assert(!found);
+		hentry->gucvar = gucvar;
+	}
 
-	guc_free(guc_variables);
-	guc_variables = guc_vars;
-	num_guc_variables = num_vars;
-	size_guc_variables = size_vars;
-	qsort((void *) guc_variables, num_guc_variables,
-		  sizeof(struct config_generic *), guc_var_compare);
+	Assert(num_vars == hash_get_num_entries(guc_hashtab));
 }
 
 /*
- * Add a new GUC variable to the list of known variables. The
- * list is expanded if needed.
+ * Add a new GUC variable to the hash of known variables. The
+ * hash is expanded if needed.
  */
 static bool
 add_guc_variable(struct config_generic *var, int elevel)
 {
-	if (num_guc_variables + 1 >= size_guc_variables)
+	GUCHashEntry *hentry;
+	bool		found;
+
+	hentry = (GUCHashEntry *) hash_search(guc_hashtab,
+										  &var->name,
+										  HASH_ENTER_NULL,
+										  &found);
+	if (unlikely(hentry == NULL))
 	{
-		/*
-		 * Increase the vector by 25%
-		 */
-		int			size_vars = size_guc_variables + size_guc_variables / 4;
-		struct config_generic **guc_vars;
-
-		if (size_vars == 0)
-		{
-			size_vars = 100;
-			guc_vars = (struct config_generic **)
-				guc_malloc(elevel, size_vars * sizeof(struct config_generic *));
-		}
-		else
-		{
-			guc_vars = (struct config_generic **)
-				guc_realloc(elevel, guc_variables, size_vars * sizeof(struct config_generic *));
-		}
-
-		if (guc_vars == NULL)
-			return false;		/* out of memory */
-
-		guc_variables = guc_vars;
-		size_guc_variables = size_vars;
+		ereport(elevel,
+				(errcode(ERRCODE_OUT_OF_MEMORY),
+				 errmsg("out of memory")));
+		return false;			/* out of memory */
 	}
-	guc_variables[num_guc_variables++] = var;
-	qsort((void *) guc_variables, num_guc_variables,
-		  sizeof(struct config_generic *), guc_var_compare);
+	Assert(!found);
+	hentry->gucvar = var;
 	return true;
 }
 
@@ -1076,23 +1138,18 @@ struct config_generic *
 find_option(const char *name, bool create_placeholders, bool skip_errors,
 			int elevel)
 {
-	const char **key = &name;
-	struct config_generic **res;
+	GUCHashEntry *hentry;
 	int			i;
 
 	Assert(name);
 
-	/*
-	 * By equating const char ** with struct config_generic *, we are assuming
-	 * the name field is first in config_generic.
-	 */
-	res = (struct config_generic **) bsearch((void *) &key,
-											 (void *) guc_variables,
-											 num_guc_variables,
-											 sizeof(struct config_generic *),
-											 guc_var_compare);
-	if (res)
-		return *res;
+	/* Look it up using the hash table. */
+	hentry = (GUCHashEntry *) hash_search(guc_hashtab,
+										  &name,
+										  HASH_FIND,
+										  NULL);
+	if (hentry)
+		return hentry->gucvar;
 
 	/*
 	 * See if the name is an obsolete name for a variable.  We assume that the
@@ -1165,7 +1222,7 @@ find_option(const char *name, bool create_placeholders, bool skip_errors,
 
 
 /*
- * comparator for qsorting and bsearching guc_variables array
+ * comparator for qsorting an array of GUC pointers
  */
 static int
 guc_var_compare(const void *a, const void *b)
@@ -1184,7 +1241,7 @@ guc_name_compare(const char *namea, const char *nameb)
 {
 	/*
 	 * The temptation to use strcasecmp() here must be resisted, because the
-	 * array ordering has to remain stable across setlocale() calls. So, build
+	 * hash mapping has to remain stable across setlocale() calls. So, build
 	 * our own with a simple ASCII-only downcasing.
 	 */
 	while (*namea && *nameb)
@@ -1206,6 +1263,42 @@ guc_name_compare(const char *namea, const char *nameb)
 	return 0;
 }
 
+/*
+ * Hash function that's compatible with guc_name_compare
+ */
+static uint32
+guc_name_hash(const void *key, Size keysize)
+{
+	uint32		result = 0;
+	const char *name = *(const char *const *) key;
+
+	while (*name)
+	{
+		char		ch = *name++;
+
+		/* Case-fold in the same way as guc_name_compare */
+		if (ch >= 'A' && ch <= 'Z')
+			ch += 'a' - 'A';
+
+		/* Merge into hash ... not very bright, but it needn't be */
+		result = pg_rotate_left32(result, 5);
+		result ^= (uint32) ch;
+	}
+	return result;
+}
+
+/*
+ * Dynahash match function to use in guc_hashtab
+ */
+static int
+guc_name_match(const void *key1, const void *key2, Size keysize)
+{
+	const char *name1 = *(const char *const *) key1;
+	const char *name2 = *(const char *const *) key2;
+
+	return guc_name_compare(name1, name2);
+}
+
 
 /*
  * Convert a GUC name to the form that should be used in pg_parameter_acl.
@@ -1275,7 +1368,8 @@ check_GUC_name_for_parameter_acl(const char *name)
 void
 InitializeGUCOptions(void)
 {
-	int			i;
+	HASH_SEQ_STATUS status;
+	GUCHashEntry *hentry;
 
 	/*
 	 * Before log_line_prefix could possibly receive a nonempty setting, make
@@ -1284,7 +1378,7 @@ InitializeGUCOptions(void)
 	pg_timezone_initialize();
 
 	/*
-	 * Create GUCMemoryContext and build sorted array of all GUC variables.
+	 * Create GUCMemoryContext and build hash table of all GUC variables.
 	 */
 	build_guc_variables();
 
@@ -1292,9 +1386,10 @@ InitializeGUCOptions(void)
 	 * Load all variables with their compiled-in defaults, and initialize
 	 * status fields as needed.
 	 */
-	for (i = 0; i < num_guc_variables; i++)
+	hash_seq_init(&status, guc_hashtab);
+	while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
 	{
-		InitializeOneGUCOption(guc_variables[i]);
+		InitializeOneGUCOption(hentry->gucvar);
 	}
 
 	guc_dirty = false;
@@ -1729,11 +1824,13 @@ pg_timezone_abbrev_initialize(void)
 void
 ResetAllOptions(void)
 {
-	int			i;
+	HASH_SEQ_STATUS status;
+	GUCHashEntry *hentry;
 
-	for (i = 0; i < num_guc_variables; i++)
+	hash_seq_init(&status, guc_hashtab);
+	while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
 	{
-		struct config_generic *gconf = guc_variables[i];
+		struct config_generic *gconf = hentry->gucvar;
 
 		/* Don't reset non-SET-able values */
 		if (gconf->context != PGC_SUSET &&
@@ -1951,7 +2048,8 @@ void
 AtEOXact_GUC(bool isCommit, int nestLevel)
 {
 	bool		still_dirty;
-	int			i;
+	HASH_SEQ_STATUS status;
+	GUCHashEntry *hentry;
 
 	/*
 	 * Note: it's possible to get here with GUCNestLevel == nestLevel-1 during
@@ -1970,9 +2068,10 @@ AtEOXact_GUC(bool isCommit, int nestLevel)
 	}
 
 	still_dirty = false;
-	for (i = 0; i < num_guc_variables; i++)
+	hash_seq_init(&status, guc_hashtab);
+	while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
 	{
-		struct config_generic *gconf = guc_variables[i];
+		struct config_generic *gconf = hentry->gucvar;
 		GucStack   *stack;
 
 		/*
@@ -2241,7 +2340,8 @@ AtEOXact_GUC(bool isCommit, int nestLevel)
 void
 BeginReportingGUCOptions(void)
 {
-	int			i;
+	HASH_SEQ_STATUS status;
+	GUCHashEntry *hentry;
 
 	/*
 	 * Don't do anything unless talking to an interactive frontend.
@@ -2264,9 +2364,10 @@ BeginReportingGUCOptions(void)
 						PGC_INTERNAL, PGC_S_OVERRIDE);
 
 	/* Transmit initial values of interesting variables */
-	for (i = 0; i < num_guc_variables; i++)
+	hash_seq_init(&status, guc_hashtab);
+	while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
 	{
-		struct config_generic *conf = guc_variables[i];
+		struct config_generic *conf = hentry->gucvar;
 
 		if (conf->flags & GUC_REPORT)
 			ReportGUCOption(conf);
@@ -2291,6 +2392,9 @@ BeginReportingGUCOptions(void)
 void
 ReportChangedGUCOptions(void)
 {
+	HASH_SEQ_STATUS status;
+	GUCHashEntry *hentry;
+
 	/* Quick exit if not (yet) enabled */
 	if (!reporting_enabled)
 		return;
@@ -2310,9 +2414,10 @@ ReportChangedGUCOptions(void)
 		return;
 
 	/* Transmit new values of interesting variables */
-	for (int i = 0; i < num_guc_variables; i++)
+	hash_seq_init(&status, guc_hashtab);
+	while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
 	{
-		struct config_generic *conf = guc_variables[i];
+		struct config_generic *conf = hentry->gucvar;
 
 		if ((conf->flags & GUC_REPORT) && (conf->status & GUC_NEEDS_REPORT))
 			ReportGUCOption(conf);
@@ -4475,25 +4580,23 @@ init_custom_variable(const char *name,
 
 /*
  * Common code for DefineCustomXXXVariable subroutines: insert the new
- * variable into the GUC variable array, replacing any placeholder.
+ * variable into the GUC variable hash, replacing any placeholder.
  */
 static void
 define_custom_variable(struct config_generic *variable)
 {
 	const char *name = variable->name;
-	const char **nameAddr = &name;
+	GUCHashEntry *hentry;
 	struct config_string *pHolder;
-	struct config_generic **res;
 
 	/*
 	 * See if there's a placeholder by the same name.
 	 */
-	res = (struct config_generic **) bsearch((void *) &nameAddr,
-											 (void *) guc_variables,
-											 num_guc_variables,
-											 sizeof(struct config_generic *),
-											 guc_var_compare);
-	if (res == NULL)
+	hentry = (GUCHashEntry *) hash_search(guc_hashtab,
+										  &name,
+										  HASH_FIND,
+										  NULL);
+	if (hentry == NULL)
 	{
 		/*
 		 * No placeholder to replace, so we can just add it ... but first,
@@ -4507,13 +4610,13 @@ define_custom_variable(struct config_generic *variable)
 	/*
 	 * This better be a placeholder
 	 */
-	if (((*res)->flags & GUC_CUSTOM_PLACEHOLDER) == 0)
+	if ((hentry->gucvar->flags & GUC_CUSTOM_PLACEHOLDER) == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_INTERNAL_ERROR),
 				 errmsg("attempt to redefine parameter \"%s\"", name)));
 
-	Assert((*res)->vartype == PGC_STRING);
-	pHolder = (struct config_string *) (*res);
+	Assert(hentry->gucvar->vartype == PGC_STRING);
+	pHolder = (struct config_string *) hentry->gucvar;
 
 	/*
 	 * First, set the variable to its default value.  We must do this even
@@ -4523,10 +4626,11 @@ define_custom_variable(struct config_generic *variable)
 	InitializeOneGUCOption(variable);
 
 	/*
-	 * Replace the placeholder. We aren't changing the name, so no re-sorting
-	 * is necessary
+	 * Replace the placeholder in the hash table.  We aren't changing the name
+	 * (at least up to case-folding), so the hash value is unchanged.
 	 */
-	*res = variable;
+	hentry->gucname = name;
+	hentry->gucvar = variable;
 
 	/*
 	 * Assign the string value(s) stored in the placeholder to the real
@@ -4818,7 +4922,8 @@ void
 MarkGUCPrefixReserved(const char *className)
 {
 	int			classLen = strlen(className);
-	int			i;
+	HASH_SEQ_STATUS status;
+	GUCHashEntry *hentry;
 	MemoryContext oldcontext;
 
 	/*
@@ -4827,9 +4932,10 @@ MarkGUCPrefixReserved(const char *className)
 	 * don't bother trying to free associated memory, since this shouldn't
 	 * happen often.)
 	 */
-	for (i = 0; i < num_guc_variables; i++)
+	hash_seq_init(&status, guc_hashtab);
+	while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
 	{
-		struct config_generic *var = guc_variables[i];
+		struct config_generic *var = hentry->gucvar;
 
 		if ((var->flags & GUC_CUSTOM_PLACEHOLDER) != 0 &&
 			strncmp(className, var->name, classLen) == 0 &&
@@ -4841,9 +4947,10 @@ MarkGUCPrefixReserved(const char *className)
 							var->name),
 					 errdetail("\"%s\" is now a reserved prefix.",
 							   className)));
-			num_guc_variables--;
-			memmove(&guc_variables[i], &guc_variables[i + 1],
-					(num_guc_variables - i) * sizeof(struct config_generic *));
+			hash_search(guc_hashtab,
+						&var->name,
+						HASH_REMOVE,
+						NULL);
 		}
 	}
 
@@ -4864,6 +4971,8 @@ struct config_generic **
 get_explain_guc_options(int *num)
 {
 	struct config_generic **result;
+	HASH_SEQ_STATUS status;
+	GUCHashEntry *hentry;
 
 	*num = 0;
 
@@ -4871,12 +4980,13 @@ get_explain_guc_options(int *num)
 	 * While only a fraction of all the GUC variables are marked GUC_EXPLAIN,
 	 * it doesn't seem worth dynamically resizing this array.
 	 */
-	result = palloc(sizeof(struct config_generic *) * num_guc_variables);
+	result = palloc(sizeof(struct config_generic *) * hash_get_num_entries(guc_hashtab));
 
-	for (int i = 0; i < num_guc_variables; i++)
+	hash_seq_init(&status, guc_hashtab);
+	while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
 	{
+		struct config_generic *conf = hentry->gucvar;
 		bool		modified;
-		struct config_generic *conf = guc_variables[i];
 
 		/* return only parameters marked for inclusion in explain */
 		if (!(conf->flags & GUC_EXPLAIN))
@@ -4979,15 +5089,6 @@ GetConfigOptionByName(const char *name, const char **varname, bool missing_ok)
 	return ShowGUCOption(record, true);
 }
 
-/*
- * Return the total number of GUC variables
- */
-int
-GetNumConfigOptions(void)
-{
-	return num_guc_variables;
-}
-
 /*
  * ShowGUCOption: get string value of variable
  *
@@ -5189,7 +5290,8 @@ write_nondefault_variables(GucContext context)
 {
 	int			elevel;
 	FILE	   *fp;
-	int			i;
+	HASH_SEQ_STATUS status;
+	GUCHashEntry *hentry;
 
 	Assert(context == PGC_POSTMASTER || context == PGC_SIGHUP);
 
@@ -5208,9 +5310,10 @@ write_nondefault_variables(GucContext context)
 		return;
 	}
 
-	for (i = 0; i < num_guc_variables; i++)
+	hash_seq_init(&status, guc_hashtab);
+	while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
 	{
-		write_one_nondefault_variable(fp, guc_variables[i]);
+		write_one_nondefault_variable(fp, hentry->gucvar);
 	}
 
 	if (FreeFile(fp))
@@ -5486,15 +5589,17 @@ Size
 EstimateGUCStateSpace(void)
 {
 	Size		size;
-	int			i;
+	HASH_SEQ_STATUS status;
+	GUCHashEntry *hentry;
 
 	/* Add space reqd for saving the data size of the guc state */
 	size = sizeof(Size);
 
 	/* Add up the space needed for each GUC variable */
-	for (i = 0; i < num_guc_variables; i++)
+	hash_seq_init(&status, guc_hashtab);
+	while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
 		size = add_size(size,
-						estimate_variable_size(guc_variables[i]));
+						estimate_variable_size(hentry->gucvar));
 
 	return size;
 }
@@ -5633,15 +5738,17 @@ SerializeGUCState(Size maxsize, char *start_address)
 	char	   *curptr;
 	Size		actual_size;
 	Size		bytes_left;
-	int			i;
+	HASH_SEQ_STATUS status;
+	GUCHashEntry *hentry;
 
 	/* Reserve space for saving the actual size of the guc state */
 	Assert(maxsize > sizeof(actual_size));
 	curptr = start_address + sizeof(actual_size);
 	bytes_left = maxsize - sizeof(actual_size);
 
-	for (i = 0; i < num_guc_variables; i++)
-		serialize_variable(&curptr, &bytes_left, guc_variables[i]);
+	hash_seq_init(&status, guc_hashtab);
+	while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
+		serialize_variable(&curptr, &bytes_left, hentry->gucvar);
 
 	/* Store actual size without assuming alignment of start_address. */
 	actual_size = maxsize - bytes_left - sizeof(actual_size);
@@ -5726,7 +5833,8 @@ RestoreGUCState(void *gucstate)
 	char	   *srcptr = (char *) gucstate;
 	char	   *srcend;
 	Size		len;
-	int			i;
+	HASH_SEQ_STATUS status;
+	GUCHashEntry *hentry;
 	ErrorContextCallback error_context_callback;
 
 	/*
@@ -5751,9 +5859,10 @@ RestoreGUCState(void *gucstate)
 	 * also ensures that set_config_option won't refuse to set them because of
 	 * source-priority comparisons.
 	 */
-	for (i = 0; i < num_guc_variables; i++)
+	hash_seq_init(&status, guc_hashtab);
+	while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
 	{
-		struct config_generic *gconf = guc_variables[i];
+		struct config_generic *gconf = hentry->gucvar;
 
 		/* Do nothing if non-shippable or if already at PGC_S_DEFAULT. */
 		if (can_skip_gucvar(gconf))
diff --git a/src/backend/utils/misc/guc_funcs.c b/src/backend/utils/misc/guc_funcs.c
index 3d2df18659..02d3b21fdc 100644
--- a/src/backend/utils/misc/guc_funcs.c
+++ b/src/backend/utils/misc/guc_funcs.c
@@ -458,13 +458,15 @@ ShowGUCConfigOption(const char *name, DestReceiver *dest)
 static void
 ShowAllGUCConfig(DestReceiver *dest)
 {
-	int			i;
+	struct config_generic **guc_vars;
+	int			num_vars;
 	TupOutputState *tstate;
 	TupleDesc	tupdesc;
 	Datum		values[3];
 	bool		isnull[3] = {false, false, false};
-	struct config_generic **guc_variables = get_guc_variables();
-	int			num_guc_variables = GetNumConfigOptions();
+
+	/* collect the variables, in sorted order */
+	guc_vars = get_guc_variables(&num_vars);
 
 	/* need a tuple descriptor representing three TEXT columns */
 	tupdesc = CreateTemplateTupleDesc(3);
@@ -478,9 +480,9 @@ ShowAllGUCConfig(DestReceiver *dest)
 	/* prepare for projection of tuples */
 	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
 
-	for (i = 0; i < num_guc_variables; i++)
+	for (int i = 0; i < num_vars; i++)
 	{
-		struct config_generic *conf = guc_variables[i];
+		struct config_generic *conf = guc_vars[i];
 		char	   *setting;
 
 		if ((conf->flags & GUC_NO_SHOW_ALL) ||
@@ -571,20 +573,13 @@ pg_settings_get_flags(PG_FUNCTION_ARGS)
 }
 
 /*
- * Return GUC variable value by variable number; optionally return canonical
- * form of name.  Return value is palloc'd.
+ * Extract fields to show in pg_settings for given variable.
  */
 static void
-GetConfigOptionByNum(int varnum, const char **values, bool *noshow)
+GetConfigOptionValues(struct config_generic *conf, const char **values,
+					  bool *noshow)
 {
 	char		buffer[256];
-	struct config_generic *conf;
-	struct config_generic **guc_variables = get_guc_variables();
-
-	/* check requested variable number valid */
-	Assert((varnum >= 0) && (varnum < GetNumConfigOptions()));
-
-	conf = guc_variables[varnum];
 
 	if (noshow)
 	{
@@ -850,6 +845,8 @@ Datum
 show_all_settings(PG_FUNCTION_ARGS)
 {
 	FuncCallContext *funcctx;
+	struct config_generic **guc_vars;
+	int			num_vars;
 	TupleDesc	tupdesc;
 	int			call_cntr;
 	int			max_calls;
@@ -914,8 +911,14 @@ show_all_settings(PG_FUNCTION_ARGS)
 		attinmeta = TupleDescGetAttInMetadata(tupdesc);
 		funcctx->attinmeta = attinmeta;
 
+		/* collect the variables, in sorted order */
+		guc_vars = get_guc_variables(&num_vars);
+
+		/* use user_fctx to remember the array location */
+		funcctx->user_fctx = guc_vars;
+
 		/* total number of tuples to be returned */
-		funcctx->max_calls = GetNumConfigOptions();
+		funcctx->max_calls = num_vars;
 
 		MemoryContextSwitchTo(oldcontext);
 	}
@@ -923,6 +926,7 @@ show_all_settings(PG_FUNCTION_ARGS)
 	/* stuff done on every call of the function */
 	funcctx = SRF_PERCALL_SETUP();
 
+	guc_vars = (struct config_generic **) funcctx->user_fctx;
 	call_cntr = funcctx->call_cntr;
 	max_calls = funcctx->max_calls;
 	attinmeta = funcctx->attinmeta;
@@ -939,7 +943,8 @@ show_all_settings(PG_FUNCTION_ARGS)
 		 */
 		do
 		{
-			GetConfigOptionByNum(call_cntr, (const char **) values, &noshow);
+			GetConfigOptionValues(guc_vars[call_cntr], (const char **) values,
+								  &noshow);
 			if (noshow)
 			{
 				/* bump the counter and get the next config setting */
diff --git a/src/backend/utils/misc/help_config.c b/src/backend/utils/misc/help_config.c
index 61c83f3590..59d2e36548 100644
--- a/src/backend/utils/misc/help_config.c
+++ b/src/backend/utils/misc/help_config.c
@@ -49,11 +49,10 @@ GucInfoMain(void)
 	int			numOpts,
 				i;
 
-	/* Initialize the guc_variables[] array */
+	/* Initialize the GUC hash table */
 	build_guc_variables();
 
-	guc_vars = get_guc_variables();
-	numOpts = GetNumConfigOptions();
+	guc_vars = get_guc_variables(&numOpts);
 
 	for (i = 0; i < numOpts; i++)
 	{
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 47d024ab5a..014aad6d2e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -394,7 +394,6 @@ extern int	set_config_option_ext(const char *name, const char *value,
 extern void AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt);
 extern char *GetConfigOptionByName(const char *name, const char **varname,
 								   bool missing_ok);
-extern int	GetNumConfigOptions(void);
 
 extern void ProcessGUCArray(ArrayType *array,
 							GucContext context, GucSource source, GucAction action);
diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h
index b3d2a959c3..99740e7e48 100644
--- a/src/include/utils/guc_tables.h
+++ b/src/include/utils/guc_tables.h
@@ -281,7 +281,7 @@ extern struct config_generic **get_explain_guc_options(int *num);
 extern char *ShowGUCOption(struct config_generic *record, bool use_units);
 
 /* get the current set of variables */
-extern struct config_generic **get_guc_variables(void);
+extern struct config_generic **get_guc_variables(int *num_vars);
 
 extern void build_guc_variables(void);
 
