/*++ Copyright (c) 2011 Microsoft Corporation Module Name: chashtable.cpp Abstract: Hashtable with chaining. Author: Leonardo de Moura (leonardo) 2011-04-14. Revision History: --*/ #include "util/chashtable.h" #include "util/hashtable.h" #include "util/hash.h" #include "util/util.h" #include #include #include typedef chashtable > int_table; typedef cmap > int_map; template class chashtable >; template class cmap >; template static void display(T const & beg, T const & end) { for (T it = beg; it != end; ++it) std::cout << *it << " "; std::cout << "\n"; } static void tst1() { int_table t; t.insert(10); ENSURE(t.contains(10)); t.insert(20); ENSURE(t.contains(20)); t.insert(30); ENSURE(t.contains(30)); ENSURE(t.size() == 3); display(t.begin(), t.end()); t.erase(20); ENSURE(!t.contains(20)); ENSURE(t.size() == 2); } struct dummy_hash { unsigned operator()(int v) const { return v % 2; } }; typedef chashtable > dint_table; template class chashtable >; static void tst2() { dint_table t; t.insert(10); t.insert(12); ENSURE(t.used_slots() == 1); display(t.begin(), t.end()); t.insert(13); display(t.begin(), t.end()); ENSURE(t.used_slots() == 2); t.insert(14); ENSURE(t.used_slots() == 2); ENSURE(t.size() == 4); display(t.begin(), t.end()); t.erase(12); ENSURE(!t.contains(12)); ENSURE(t.size() == 3); ENSURE(t.contains(10)); ENSURE(!t.contains(12)); ENSURE(t.contains(14)); ENSURE(t.contains(13)); t.insert(16); ENSURE(t.size() == 4); t.insert(18); ENSURE(t.size() == 5); ENSURE(t.used_slots() == 2); display(t.begin(), t.end()); t.erase(10); display(t.begin(), t.end()); ENSURE(!t.contains(10)); ENSURE(!t.contains(12)); ENSURE(t.contains(14)); ENSURE(t.contains(13)); ENSURE(t.contains(16)); ENSURE(t.contains(18)); } static void tst3() { dint_table t; t.insert(10); t.insert(12); ENSURE(t.used_slots() == 1); ENSURE(t.contains(10)); ENSURE(t.contains(12)); t.erase(12); t.erase(10); ENSURE(t.empty()); ENSURE(t.empty()); ENSURE(t.used_slots() == 0); t.insert(10); ENSURE(t.used_slots() == 1); ENSURE(t.contains(10)); ENSURE(t.size() == 1); } typedef int_hashtable > int_set; template static void tst4(unsigned num, unsigned N) { int_set s; T t; for (unsigned i = 0; i < num; ++i) { int v = rand() % N; if (rand() % 3 == 2) { TRACE(chashtable, tout << "erase " << v << "\n";); s.erase(v); t.erase(v); ENSURE(!t.contains(v)); } else { TRACE(chashtable, tout << "insert " << v << "\n";); s.insert(v); t.insert(v); ENSURE(t.contains(v)); } ENSURE(s.size() == t.size()); ENSURE(s.empty() == t.empty()); } std::cout << "size: " << s.size() << " " << t.size() << "\n"; int_set::iterator it1 = s.begin(); int_set::iterator end1 = s.end(); for(; it1 != end1; ++it1) { ENSURE(t.contains(*it1)); } typename T::iterator it2 = t.begin(); typename T::iterator end2 = t.end(); for(; it2 != end2; ++it2) { ENSURE(s.contains(*it2)); ENSURE(t.contains(*it2)); } } static void tst5() { dint_table t; t.insert(4); t.insert(9); t.insert(8); t.insert(1); t.erase(1); t.insert(7); t.insert(1); t.insert(2); } static void tst6() { int_map m; m.insert(10, 4); ENSURE(m.contains(10)); DEBUG_CODE({ int r; ENSURE(m.find(10, r) && r == 4); }); } 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(); tst3(); tst6(); tst4(1000,10); tst4(10000,10); tst4(50000,1000); tst_combine_hash_low_bits(); tst5(); }