diff --git a/src/test/chashtable.cpp b/src/test/chashtable.cpp index 9675ee312a..238c5cda20 100644 --- a/src/test/chashtable.cpp +++ b/src/test/chashtable.cpp @@ -21,6 +21,8 @@ Revision History: #include "util/hash.h" #include "util/util.h" #include +#include +#include typedef chashtable > int_table; typedef cmap > int_map; @@ -172,6 +174,87 @@ static void tst6() { }); } +static unsigned combine_hash_old(unsigned h1, unsigned h2) { + // Pre-change combine_hash implementation kept for A/B quality checks. + h2 -= h1; h2 ^= (h1 << 8); + h1 -= h2; h2 ^= (h1 << 16); + h2 -= h1; h2 ^= (h1 << 10); + return h2; +} + +constexpr unsigned hash_compare_num_samples = 1u << 16; +constexpr unsigned hash_compare_seed = 0x12345678u; +constexpr unsigned hash_compare_alignment_shift = 12; + +struct hash_projection_stats_t { + unsigned m_occupied = 0; + uint64_t m_uniform_error = 0; +}; + +template +static hash_projection_stats_t get_projection_stats(CombineHash const& combine, unsigned bits, bool low_bits) { + // Project hashes to either a suffix (low bits) or prefix (high bits), then + // collect occupancy and a scaled squared-error uniformity score. + SASSERT(bits <= 16); + unsigned const num_buckets = 1u << bits; + unsigned const mask = num_buckets - 1; + int64_t const signed_num_samples = static_cast(hash_compare_num_samples); + int64_t const signed_num_buckets = static_cast(num_buckets); + std::vector counts(num_buckets, 0); + for (unsigned i = 0; i < hash_compare_num_samples; ++i) { + unsigned h = combine(i << hash_compare_alignment_shift, hash_compare_seed); + unsigned b = low_bits ? (h & mask) : (h >> (32 - bits)); + counts[b]++; + } + + hash_projection_stats_t stats; + for (unsigned c : counts) { + if (c != 0) + ++stats.m_occupied; + // Scaled squared deviation from ideal per-bucket load num_samples/num_buckets. + int64_t diff = static_cast(c) * signed_num_buckets - signed_num_samples; + stats.m_uniform_error += static_cast(diff * diff); + } + return stats; +} + +static void tst_combine_hash_low_bits() { + constexpr unsigned bits8 = 8; + constexpr unsigned bits16 = 16; + + auto old_low8 = get_projection_stats(combine_hash_old, bits8, true); + auto new_low8 = get_projection_stats(combine_hash, bits8, true); + auto old_low16 = get_projection_stats(combine_hash_old, bits16, true); + auto new_low16 = get_projection_stats(combine_hash, bits16, true); + auto old_high8 = get_projection_stats(combine_hash_old, bits8, false); + auto new_high8 = get_projection_stats(combine_hash, bits8, false); + auto old_high16 = get_projection_stats(combine_hash_old, bits16, false); + auto new_high16 = get_projection_stats(combine_hash, bits16, false); + + unsigned old_low8_collisions = hash_compare_num_samples - old_low8.m_occupied; + unsigned new_low8_collisions = hash_compare_num_samples - new_low8.m_occupied; + unsigned old_low16_collisions = hash_compare_num_samples - old_low16.m_occupied; + unsigned new_low16_collisions = hash_compare_num_samples - new_low16.m_occupied; + + ENSURE(new_low8_collisions < old_low8_collisions); + ENSURE(new_low16_collisions < old_low16_collisions); + ENSURE(new_low8.m_uniform_error < old_low8.m_uniform_error); + ENSURE(new_low16.m_uniform_error < old_low16.m_uniform_error); + + std::cout << "combine_hash old/new low8 collisions: " + << old_low8_collisions << "/" << new_low8_collisions << "\n"; + std::cout << "combine_hash old/new low16 collisions: " + << old_low16_collisions << "/" << new_low16_collisions << "\n"; + std::cout << "combine_hash old/new low8 uniform_error: " + << old_low8.m_uniform_error << "/" << new_low8.m_uniform_error << "\n"; + std::cout << "combine_hash old/new low16 uniform_error: " + << old_low16.m_uniform_error << "/" << new_low16.m_uniform_error << "\n"; + std::cout << "combine_hash old/new high8 uniform_error: " + << old_high8.m_uniform_error << "/" << new_high8.m_uniform_error << "\n"; + std::cout << "combine_hash old/new high16 uniform_error: " + << old_high16.m_uniform_error << "/" << new_high16.m_uniform_error << "\n"; +} + void tst_chashtable() { tst1(); tst2(); @@ -180,5 +263,6 @@ void tst_chashtable() { tst4(1000,10); tst4(10000,10); tst4(50000,1000); + tst_combine_hash_low_bits(); tst5(); } diff --git a/src/util/hash.h b/src/util/hash.h index ac6896bf19..40ea7bfb89 100644 --- a/src/util/hash.h +++ b/src/util/hash.h @@ -57,10 +57,8 @@ static inline unsigned hash_ull(unsigned long long a) { } static inline unsigned combine_hash(unsigned h1, unsigned h2) { - h2 -= h1; h2 ^= (h1 << 8); - h1 -= h2; h2 ^= (h1 << 16); - h2 -= h1; h2 ^= (h1 << 10); - return h2; + h1 ^= h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2); + return hash_u(h1); } static inline unsigned hash_u_u(unsigned a, unsigned b) { @@ -256,4 +254,3 @@ static inline unsigned mk_mix(unsigned a, unsigned b, unsigned c) { return c; } -