From f6a3ddfeb049799df7b60bddb4b262c27e27e272 Mon Sep 17 00:00:00 2001
From: John Naylor <john.naylor@postgresql.org>
Date: Wed, 18 Aug 2021 12:36:19 -0400
Subject: [PATCH v1 3/6] Use hash bucket as a level-one cache to avoid walking
 the conflict chain

Since the hash buckets are now two cachelines wide, use that space
to store hashes, keys, and pointers to catcache entries. The number
of L1 entries supported depends on how many attributes are in the search
key -- from 2 for 4-att keys to 5 for 1-att keys.

If we must walk the list to find the entry, copy its data to the front
of the L1 and evict the last entry added there. However, we don't store
negative entries in the L1, since they are not as likely to be accessed
repeatedly.
---
 src/backend/utils/cache/catcache.c | 105 +++++++++++++++++++++++++++--
 1 file changed, 98 insertions(+), 7 deletions(-)

diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 55ea4b0339..dad985fc5a 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -459,6 +459,9 @@ CatCacheRemoveCTup(CatCache *cache, CatCTup *ct)
 	Assert(ct->refcount == 0);
 	Assert(ct->my_cache == cache);
 
+	/* reset L1 area of bucket */
+	memset(cache->cc_bucket, 0, offsetof(CCBucket, conflicts));
+
 	/* delink from linked list if we haven't already */
 	if (ct->cache_elem.prev)
 	{
@@ -585,6 +588,10 @@ CatCacheInvalidate(CatCache *cache, uint32 hashValue)
 	 */
 	hashIndex = HASH_INDEX(hashValue, cache->cc_nbuckets);
 	bucket = cache->cc_bucket[hashIndex];
+
+	/* reset L1 area of bucket */
+	memset(&bucket, 0, offsetof(CCBucket, conflicts));
+
 	dlist_foreach_modify(iter, &bucket.conflicts)
 	{
 		CatCTup    *ct = dlist_container(CatCTup, cache_elem, iter.cur);
@@ -668,6 +675,9 @@ ResetCatalogCache(CatCache *cache)
 	{
 		CCBucket	bucket = cache->cc_bucket[i];
 
+		/* reset L1 area of bucket */
+		memset(&bucket, 0, offsetof(CCBucket, conflicts));
+
 		dlist_foreach_modify(iter, &bucket.conflicts)
 		{
 			CatCTup    *ct = dlist_container(CatCTup, cache_elem, iter.cur);
@@ -1251,12 +1261,71 @@ SearchCatCacheInternal(CatCache *cache,
 
 	/*
 	 * scan the hash bucket until we find a match or exhaust our tuples
-	 *
-	 * Note: it's okay to use dlist_foreach here, even though we modify the
-	 * dlist within the loop, because we don't continue the loop afterwards.
 	 */
 	bucket = cache->cc_bucket[hashIndex];
 
+	/* initialize pointers into members of the CCBucket union type */
+	uint32	   *hash_array = NULL;
+	Datum	   *key_array = NULL;
+	struct catctup **ct_array = NULL;
+	int			nentries = 0;
+
+	/*
+	 * XXX this is kind of ugly -- maybe better if templated.
+	 * Not great that we throw away multi-array info.
+	 * Also nentries are magic constants that shouldn't be here.
+	 */
+	switch (nkeys)
+	{
+		case 1:
+			hash_array = bucket.bucket1.hashes;
+			key_array = (Datum *) bucket.bucket1.keys;
+			ct_array = bucket.bucket1.ct;
+			nentries = 5;
+			break;
+		case 2:
+			hash_array = bucket.bucket2.hashes;
+			key_array = (Datum *) bucket.bucket2.keys;
+			ct_array = bucket.bucket2.ct;
+			nentries = 4;
+			break;
+		case 3:
+			hash_array = bucket.bucket3.hashes;
+			key_array = (Datum *) bucket.bucket3.keys;
+			ct_array = bucket.bucket3.ct;
+			nentries = 3;
+			break;
+		case 4:
+			hash_array = bucket.bucket4.hashes;
+			key_array = (Datum *) bucket.bucket4.keys;
+			ct_array = bucket.bucket4.ct;
+			nentries = 2;
+			break;
+		default:
+			Assert(false);
+	}
+
+	/* first search within L1 area of the hash bucket */
+	for (int i = 0; i < nentries; i++)
+	{
+		if (hash_array[i] != hashValue)
+			continue;			/* quickly skip entry if wrong hash val */
+
+		if (!CatalogCacheCompareTuple(cache, nkeys, &key_array[i * nkeys], arguments))
+			continue;
+
+		/* This can happen if the search keys are zero and the L1 has empty slots. */
+		if (ct_array[i] == NULL)
+			continue;
+
+		/* found a match */
+		ct = ct_array[i];
+		Assert(ct->ct_magic == CT_MAGIC);
+		Assert(ct->cache_elem.prev != NULL);
+		goto finish;
+	}
+
+	/* not in the L1, so walk the bucket chain list */
 	dlist_foreach(iter, &bucket.conflicts)
 	{
 		ct = dlist_container(CatCTup, cache_elem, iter.cur);
@@ -1268,12 +1337,34 @@ SearchCatCacheInternal(CatCache *cache,
 			continue;
 
 		/*
-		 * We found a match in the cache.  Move it to the front of the list
-		 * for its hashbucket, in order to speed subsequent searches.  (The
+		 * We found a match in the cache.  Move it to the front of the L1 area
+		 * of its hashbucket, in order to speed subsequent searches.  (The
 		 * most frequently accessed elements in any hashbucket will tend to be
-		 * near the front of the hashbucket's list.)
+		 * in the L1 area.) We skip this for negative entries.
 		 */
-		dlist_move_head(&bucket.conflicts, &ct->cache_elem);
+		if (!ct->negative)
+		{
+			memmove(&hash_array[1],
+					hash_array,
+					sizeof(hash_array) -
+					sizeof(hash_array[0]));
+			hash_array[0] = hashValue;
+
+			memmove(&key_array[nkeys],
+					key_array,
+					sizeof(key_array) -
+					nkeys * sizeof(key_array[0]));
+			memcpy(key_array, arguments,
+				   nkeys * sizeof(key_array[0]));
+
+			memmove(&ct_array[1],
+					ct_array,
+					sizeof(ct_array) -
+					sizeof(ct_array[0]));
+			ct_array[0] = ct;
+		}
+
+finish:
 
 		/*
 		 * If it's a positive entry, bump its refcount and return it. If it's
-- 
2.31.1

