From 8117b3123216d8a3b2f2bf79804745b29733ddd1 Mon Sep 17 00:00:00 2001 From: John Naylor Date: Mon, 18 Dec 2023 11:10:28 +0700 Subject: [PATCH v13 3/6] Add optimized string hashing to hashfn_unstable.h Given an already-initialized hash state and a C-string, accumulate the hash of the string into the hash state and return the length for the caller to (optionally) save for the finalizer. This avoids a strlen call. If the string pointer is aligned, we can use a word- at-a-time algorithm both for NUL lookahead and for computing the remainder length up to the NUL. The latter was inspired by NetBSD's strlen(). The aligned case is only used on 64-bit platforms, since it's not worth the extra complexity for 32-bit. Jeff Davis and John Naylor Discussion: https://postgr.es/m/3820f030fd008ff14134b3e9ce5cc6dd623ed479.camel%40j-davis.com --- src/include/common/hashfn_unstable.h | 114 ++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 3 deletions(-) diff --git a/src/include/common/hashfn_unstable.h b/src/include/common/hashfn_unstable.h index 5e882532d2..8d8952beb3 100644 --- a/src/include/common/hashfn_unstable.h +++ b/src/include/common/hashfn_unstable.h @@ -16,6 +16,9 @@ and may differ by hardware platform. #ifndef HASHFN_UNSTABLE_H #define HASHFN_UNSTABLE_H +#include "port/pg_bitutils.h" +#include "port/pg_bswap.h" + /* * fasthash is a modification of code taken from * https://code.google.com/archive/p/fast-hash/source/default/source @@ -57,8 +60,8 @@ and may differ by hardware platform. fasthash_state hs; fasthash_init(&hs, FH_UNKNOWN_LENGTH, 0); -fasthash_accum(&hs, , ); -return fasthash_final32(&hs, ); +len = fasthash_accum_cstring(&hs, *str); +return fasthash_final32(&hs, len); */ @@ -66,11 +69,12 @@ return fasthash_final32(&hs, ); typedef struct fasthash_state { uint64 accum; -#define FH_SIZEOF_ACCUM sizeof(uint64) uint64 hash; } fasthash_state; +#define FH_SIZEOF_ACCUM 8 +StaticAssertDecl(sizeof(((fasthash_state*) 0)->accum) == FH_SIZEOF_ACCUM, "wrong size for size macro"); #define FH_UNKNOWN_LENGTH 1 @@ -151,6 +155,110 @@ fasthash_accum(fasthash_state *hs, const char *k, int len) fasthash_combine(hs); } +/* From: https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord */ +#define haszero64(v) \ + (((v) - 0x0101010101010101UL) & ~(v) & 0x8080808080808080UL) + +/* + * With an aligned pointer, we consume the string a word at a time. Loading + * the word containing the NUL terminator cannot segfault since page boundaries + * are MAXALIGN'd. For that last word, only use bytes up to the NUL for the hash. + * The algorithm was adopted from NetBSD's strlen. + */ +static inline int +fasthash_accum_cstring_aligned(fasthash_state *hs, const char *str) +{ + const char *const start = str; + const char *buf = start; + int remainder; + uint64 zero_bytes; + + Assert(PointerIsAligned(start, uint64)); + for (;;) + { + uint64 chunk = *(uint64 *)buf; + + /* + * With little-endian representation, we can use this calculation, + * which sets bits in the first byte in the result word + * that corresponds to a zero byte in the original word. + * The rest of the bytes are indeterminate, so cannot be used + * on big-endian machines without either swapping or a bytewise check. + */ +#ifdef WORDS_BIGENDIAN + zero_bytes = haszero64(pg_bswap(chunk)); +#else + zero_bytes = haszero64(chunk); +#endif + if (zero_bytes) + break; + + hs->accum = chunk; + fasthash_combine(hs); + buf += FH_SIZEOF_ACCUM; + } + + /* + * Bytes with set bits will be 0x80, so + * calculate the first occurrence of a zero byte within the input word + * by counting the number of trailing (for LE) + * zeros and dividing the result by 8. + */ + remainder = pg_rightmost_one_pos64(zero_bytes) / BITS_PER_BYTE; + fasthash_accum(hs, buf, remainder); + buf += remainder; + + return buf - start; +} + +static inline int +fasthash_accum_cstring_unaligned(fasthash_state *hs, const char *str) +{ + const char *const start = str; + const char *buf = str; + + while (*buf) + { + int chunk_len = 0; + + while (chunk_len < FH_SIZEOF_ACCUM && buf[chunk_len] != '\0') + chunk_len++; + + fasthash_accum(hs, buf, chunk_len); + buf += chunk_len; + } + + return buf - start; +} + +/* + * Accumulate the input into the hash state + * and return the length of the string. + */ +static inline int +fasthash_accum_cstring(fasthash_state *hs, const char *str) +{ +#if SIZEOF_VOID_P >= 8 + + int len; +#ifdef USE_ASSERT_CHECKING + int len_check; + fasthash_state hs_check; + + memcpy(&hs_check, hs, sizeof(fasthash_state)); + len_check = fasthash_accum_cstring_unaligned(&hs_check, str); +#endif + if (PointerIsAligned(str, uint64)) + { + len = fasthash_accum_cstring_aligned(hs, str); + Assert(hs_check.hash == hs->hash && len_check == len); + return len; + } +#endif /* SIZEOF_VOID_P */ + + return fasthash_accum_cstring_unaligned(hs, str); +} + /* * The finalizer * -- 2.43.0