From c2b799dd2418fb68fcfc6ccf006a50f74c9072fe Mon Sep 17 00:00:00 2001
From: John Naylor <john.naylor@postgresql.org>
Date: Wed, 29 Nov 2023 16:28:58 +0700
Subject: [PATCH v5 4/4] Add bytewise interface for incrementing the hash state

This is the nicest interface for guc_name_hash, and seems
good for string_hash too. It's not clear if this is good for
the latter from a performance perspective.
---
 src/backend/utils/hash/dynahash.c    | 14 +++--------
 src/backend/utils/misc/guc.c         | 16 ++++++-------
 src/include/common/hashfn_unstable.h | 35 ++++++++++++++++++++++++++++
 3 files changed, 46 insertions(+), 19 deletions(-)

diff --git a/src/backend/utils/hash/dynahash.c b/src/backend/utils/hash/dynahash.c
index 1c08dc8942..ab2dbefd12 100644
--- a/src/backend/utils/hash/dynahash.c
+++ b/src/backend/utils/hash/dynahash.c
@@ -328,19 +328,11 @@ string_hash(const void *key, Size keysize)
 
 	fasthash64_init(&hs, 0);
 
-	while (*buf)
+	while (*buf && s_len < keysize)
 	{
-		int chunk_len = 0;
+		s_len++;
 
-		for (int i = 0;
-			i < 8 && *buf++ && s_len < keysize;
-			i++)
-		{
-			chunk_len++;
-			s_len++;
-		}
-
-		fasthash64_accum(&hs, buf, chunk_len);
+		fasthash64_accum_byte(&hs, *buf);
 	}
 
 	return fasthash64_final32(&hs);
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 82d8efbc96..2428f2475c 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -33,6 +33,7 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_parameter_acl.h"
+#include "common/hashfn_unstable.h"
 #include "guc_internal.h"
 #include "libpq/pqformat.h"
 #include "parser/scansup.h"
@@ -1324,22 +1325,21 @@ guc_name_compare(const char *namea, const char *nameb)
 static uint32
 guc_name_hash(const void *key, Size keysize)
 {
-	uint32		result = 0;
 	const char *name = *(const char *const *) key;
+	fasthash64_state hs;
+
+	fasthash64_init(&hs, 0);
 
 	while (*name)
 	{
 		char		ch = *name++;
 
-		/* Case-fold in the same way as guc_name_compare */
-		if (ch >= 'A' && ch <= 'Z')
-			ch += 'a' - 'A';
+		/* quick and dirty case-folding suitable for hashing */
+		ch |= 0x20;
 
-		/* Merge into hash ... not very bright, but it needn't be */
-		result = pg_rotate_left32(result, 5);
-		result ^= (uint32) ch;
+		fasthash64_accum_byte(&hs, ch);
 	}
-	return result;
+	return fasthash64_final32(&hs);
 }
 
 /*
diff --git a/src/include/common/hashfn_unstable.h b/src/include/common/hashfn_unstable.h
index a95942f7af..1b7db5ac07 100644
--- a/src/include/common/hashfn_unstable.h
+++ b/src/include/common/hashfn_unstable.h
@@ -26,6 +26,7 @@
 typedef struct fasthash64_state
 {
 	uint64 accum;
+	int8	accum_len;
 	uint64 hash;
 } fasthash64_state;
 
@@ -46,6 +47,31 @@ void fasthash64_init(fasthash64_state *hs, uint64_t seed)
 	// setting seed would go here
 }
 
+static inline
+void fasthash64_accum_byte(fasthash64_state *hs, const unsigned char ch)
+{
+	const uint64_t    m = 0x880355f21e6d1965ULL;
+
+	hs->accum |= ch;
+
+	// wip: is there a better way to get sizeof struct member?
+	if (hs->accum_len == sizeof(((fasthash64_state *) 0)->accum))
+	{
+		// combine into hash
+		hs->hash ^= mix(hs->accum);
+		hs->hash *= m;
+
+		// reset accum
+		hs->accum = 0;
+		hs->accum_len = 0;
+	}
+	else
+	{
+		hs->accum <<= sizeof(unsigned char);
+		hs->accum_len += sizeof(unsigned char);
+	}
+}
+
 static inline
 void fasthash64_accum(fasthash64_state *hs, const void *buf, int len)
 {
@@ -94,6 +120,15 @@ void fasthash64_accum(fasthash64_state *hs, const void *buf, int len)
 static inline
 uint64_t fasthash64_final(fasthash64_state *hs)
 {
+	const uint64_t    m = 0x880355f21e6d1965ULL;
+
+	// check for remaining bytes to combine into hash
+	if (hs->accum_len > 0)
+	{
+		hs->hash ^= mix(hs->accum);
+		hs->hash *= m;
+	}
+
 	return mix(hs->hash);
 }
 
-- 
2.42.0

