From 8b5e905cbffa9eadd1e0d4fa6af7e7b77a93b7c7 Mon Sep 17 00:00:00 2001
From: Jeff Davis <jeff@j-davis.com>
Date: Thu, 19 Oct 2023 15:48:16 -0700
Subject: [PATCH v7 5/6] Optimize SearchPathCache by saving the last entry.

Repeated lookups are common, so it's worth it to check the last entry
before doing another hash lookup.

Discussion: https://postgr.es/m/04c8592dbd694e4114a3ed87139a7a04e4363030.camel%40j-davis.com
---
 src/backend/catalog/namespace.c | 107 ++++++++++++++++++++------------
 1 file changed, 67 insertions(+), 40 deletions(-)

diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 68deb86554..ff7169dc99 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -240,7 +240,8 @@ static bool MatchNamedCall(HeapTuple proctup, int nargs, List *argnames,
  *
  * The search path cache is based on a wrapper around a simplehash hash table
  * (nsphash, defined below). The spcache wrapper deals with OOM while trying
- * to initialize a key, and also offers a more convenient API.
+ * to initialize a key, optimizes repeated lookups of the same key, and also
+ * offers a more convenient API.
  */
 
 static inline uint32
@@ -280,6 +281,7 @@ spcachekey_equal(SearchPathCacheKey a, SearchPathCacheKey b)
 #define SPCACHE_RESET_THRESHOLD		1024
 
 static nsphash_hash *SearchPathCache = NULL;
+static SearchPathCacheEntry *LastSearchPathCacheEntry = NULL;
 
 /*
  * Create search path cache.
@@ -308,6 +310,7 @@ spcache_reset(void)
 
 	MemoryContextReset(SearchPathCacheContext);
 	SearchPathCache = NULL;
+	LastSearchPathCacheEntry = NULL;
 
 	spcache_init();
 }
@@ -325,12 +328,25 @@ spcache_members(void)
 static SearchPathCacheEntry *
 spcache_lookup(const char *searchPath, Oid roleid)
 {
-	SearchPathCacheKey		 cachekey = {
-		.searchPath					  = searchPath,
-		.roleid						  = roleid
-	};
+	if (LastSearchPathCacheEntry &&
+		LastSearchPathCacheEntry->key.roleid == roleid &&
+		strcmp(LastSearchPathCacheEntry->key.searchPath, searchPath) == 0)
+	{
+		return LastSearchPathCacheEntry;
+	}
+	else
+	{
+		SearchPathCacheEntry	*entry;
+		SearchPathCacheKey		 cachekey = {
+			.searchPath					  = searchPath,
+			.roleid						  = roleid
+		};
+
+		entry = nsphash_lookup(SearchPathCache, cachekey);
 
-	return nsphash_lookup(SearchPathCache, cachekey);
+		LastSearchPathCacheEntry = entry;
+		return entry;
+	}
 }
 
 /*
@@ -342,48 +358,59 @@ spcache_lookup(const char *searchPath, Oid roleid)
 static SearchPathCacheEntry *
 spcache_insert(const char *searchPath, Oid roleid)
 {
-	SearchPathCacheEntry	*entry;
-	bool					 found;
-	SearchPathCacheKey		 cachekey = {
-		.searchPath					  = searchPath,
-		.roleid						  = roleid
-	};
+	if (LastSearchPathCacheEntry &&
+		LastSearchPathCacheEntry->key.roleid == roleid &&
+		strcmp(LastSearchPathCacheEntry->key.searchPath, searchPath) == 0)
+	{
+		return LastSearchPathCacheEntry;
+	}
+	else
+	{
+		SearchPathCacheEntry	*entry;
+		bool					 found;
+		SearchPathCacheKey		 cachekey = {
+			.searchPath					  = searchPath,
+			.roleid						  = roleid
+		};
 
-	entry = nsphash_insert(SearchPathCache, cachekey, &found);
+		entry = nsphash_insert(SearchPathCache, cachekey, &found);
 
-	/* ensure that key is initialized and the rest is zeroed */
-	if (!found)
-	{
-		size_t	 size = strlen(searchPath) + 1;
-		char	*newstr;
+		/* ensure that key is initialized and the rest is zeroed */
+		if (!found)
+		{
+			size_t	 size = strlen(searchPath) + 1;
+			char	*newstr;
 
-		/* do not touch entry->status, used by simplehash */
-		entry->oidlist = NIL;
-		entry->finalPath = NIL;
-		entry->firstNS = InvalidOid;
-		entry->temp_missing = false;
+			/* do not touch entry->status, used by simplehash */
+			entry->oidlist = NIL;
+			entry->finalPath = NIL;
+			entry->firstNS = InvalidOid;
+			entry->temp_missing = false;
 
-		newstr = MemoryContextAllocExtended(SearchPathCacheContext, size,
-											MCXT_ALLOC_NO_OOM);
+			newstr = MemoryContextAllocExtended(SearchPathCacheContext, size,
+												MCXT_ALLOC_NO_OOM);
 
-		/* if we can't initialize the key due to OOM, delete the entry */
-		if (newstr == NULL)
-		{
-			nsphash_delete_item(SearchPathCache, entry);
-			MemoryContextStats(TopMemoryContext);
-			ereport(ERROR,
-					(errcode(ERRCODE_OUT_OF_MEMORY),
-					 errmsg("out of memory"),
-					 errdetail("Failed on request of size %zu in memory context \"%s\".",
-							   size, SearchPathCacheContext->name)));
+			/* if we can't initialize the key due to OOM, delete the entry */
+			if (newstr == NULL)
+			{
+				nsphash_delete_item(SearchPathCache, entry);
+				MemoryContextStats(TopMemoryContext);
+				ereport(ERROR,
+						(errcode(ERRCODE_OUT_OF_MEMORY),
+						 errmsg("out of memory"),
+						 errdetail("Failed on request of size %zu in memory context \"%s\".",
+								   size, SearchPathCacheContext->name)));
+
+			}
+
+			memcpy(newstr, searchPath, size);
+			entry->key.searchPath = newstr;
+			entry->key.roleid = roleid;
 		}
 
-		memcpy(newstr, searchPath, size);
-		entry->key.searchPath = newstr;
-		entry->key.roleid = roleid;
+		LastSearchPathCacheEntry = entry;
+		return entry;
 	}
-
-	return entry;
 }
 
 /*
-- 
2.34.1

