From 86797b6402c37c9285cdcc4f588320bd9a9a90f5 Mon Sep 17 00:00:00 2001 From: Davi Reis Date: Wed, 14 Mar 2012 01:29:13 -0300 Subject: [PATCH] Finally beat STL. Trying improvement around cuckoo hashing idea. --- cxxmph/bm_map.cc | 7 +++--- cxxmph/mph_index.h | 43 +++++++++++++++++++++++++++++++---- cxxmph/mph_map.h | 54 +++++++++++++++++++++++++++++++++----------- cxxmph/seeded_hash.h | 9 ++++---- 4 files changed, 87 insertions(+), 26 deletions(-) diff --git a/cxxmph/bm_map.cc b/cxxmph/bm_map.cc index 8195217..0a0b225 100644 --- a/cxxmph/bm_map.cc +++ b/cxxmph/bm_map.cc @@ -49,6 +49,7 @@ class BM_SearchUrls : public SearchUrlsBenchmark { mymap_[*it] = *it; } mymap_.rehash(mymap_.bucket_count()); + fprintf(stderr, "Occupation: %f\n", static_cast(mymap_.size())/mymap_.bucket_count()); return true; } MapType mymap_; @@ -57,7 +58,7 @@ class BM_SearchUrls : public SearchUrlsBenchmark { template class BM_SearchUint64 : public SearchUint64Benchmark { public: - BM_SearchUint64() : SearchUint64Benchmark(10000, 10*1000*1000) { } + BM_SearchUint64() : SearchUint64Benchmark(100000, 10*1000*1000) { } virtual bool SetUp() { if (!SearchUint64Benchmark::SetUp()) return false; for (int i = 0; i < values_.size(); ++i) { @@ -88,15 +89,13 @@ using namespace cxxmph; int main(int argc, char** argv) { srandom(4); - /* Benchmark::Register(new BM_CreateUrls>("URLS100k")); Benchmark::Register(new BM_CreateUrls>("URLS100k")); Benchmark::Register(new BM_SearchUrls>("URLS100k", 10*1000 * 1000, 0)); Benchmark::Register(new BM_SearchUrls>("URLS100k", 10*1000 * 1000, 0)); Benchmark::Register(new BM_SearchUrls>("URLS100k", 10*1000 * 1000, 0.9)); Benchmark::Register(new BM_SearchUrls>("URLS100k", 10*1000 * 1000, 0.9)); - */ - Benchmark::Register(new BM_SearchUint64>); Benchmark::Register(new BM_SearchUint64>); + Benchmark::Register(new BM_SearchUint64>); Benchmark::RunAll(); } diff --git a/cxxmph/mph_index.h b/cxxmph/mph_index.h index f2741ea..45390a4 100644 --- a/cxxmph/mph_index.h +++ b/cxxmph/mph_index.h @@ -25,6 +25,7 @@ #include #include +#include #include #include // for std::hash #include @@ -63,6 +64,12 @@ class MPHIndex { template // must agree with Reset uint32_t minimal_perfect_hash(const Key& x) const; + // Crazy functions. Ignore. + template // must agree with Reset + uint32_t cuckoo_hash(const Key& x, const uint32_t* h, uint8_t nest) const; + template // must agree with Reset + void hash_vector(const Key& x, uint32_t* h) const; + // Serialization for mmap usage - not tested well, ping me if you care. // Serialized tables are not guaranteed to work across versions or different // endianness (although they could easily be made to be). @@ -94,6 +101,8 @@ class MPHIndex { // Partition vertex count, derived from c parameter. uint32_t r_; + uint32_t nest_displacement_[3]; // derived from r_ + // The array containing the minimal perfect hash function graph. Do not use // c++ vector to make mmap based backing easier. const uint8_t* g_; @@ -118,6 +127,16 @@ class MPHIndex { }; +template +T nexthigher(T k) { + if (k == 0) + return 1; + k--; + for (int i=1; i> i; + return k+1; +} + // Template method needs to go in the header file. template bool MPHIndex::Reset( @@ -129,6 +148,13 @@ bool MPHIndex::Reset( m_ = size; r_ = static_cast(ceil((c_*m_)/3)); if ((r_ % 2) == 0) r_ += 1; + nest_displacement_[0] = 0; + nest_displacement_[1] = r_; + nest_displacement_[2] = (r_ << 1); + // This can be used to speed mods, but increases occupation too much. + // Needs to try http://gmplib.org/manual/Integer-Exponentiation.html instead + // r_ = nexthigher(r_); + n_ = 3*r_; k_ = 1U << b_; @@ -174,15 +200,24 @@ bool MPHIndex::Mapping( return false; } +template +uint32_t MPHIndex::cuckoo_hash(const Key& key, const uint32_t* h, uint8_t nest) const { + return (h[nest] % r_) + nest_displacement_[nest]; +} + +template +void MPHIndex::hash_vector(const Key& key, uint32_t* h) const { + SeededHashFcn().hash64(key, hash_seed_[0], reinterpret_cast(&h)); +} + template uint32_t MPHIndex::perfect_hash(const Key& key) const { uint32_t h[4]; SeededHashFcn().hash64(key, hash_seed_[0], reinterpret_cast(&h)); // for (int i = 0; i < 3; ++i) h[i] = SeededHashFcn()(key, hash_seed_[i]); - assert(r_); - h[0] = h[0] % r_; - h[1] = h[1] % r_ + r_; - h[2] = h[2] % r_ + (r_ << 1); + h[0] = (h[0] % r_) + nest_displacement_[0]; + h[1] = (h[1] % r_) + nest_displacement_[1]; + h[2] = (h[2] % r_) + nest_displacement_[2]; assert(g_size_); // cerr << "g_.size() " << g_size_ << " h0 >> 2 " << (h[0] >> 2) << endl; assert((h[0] >> 2) >(&values_, &present_, it); } + static void set_2bit_value(uint8_t *d, uint32_t i, uint8_t v) { + d[(i >> 2)] &= ((v << ((i & 3) << 1)) | valuemask[i & 3]); + } + static uint32_t get_2bit_value(const uint8_t* d, uint32_t i) { + return (d[(i >> 2)] >> (((i & 3) << 1)) & 3); + } + void pack(); std::vector values_; std::vector present_; + const uint8_t* nests_; SimpleMPHIndex::hash_function> index_; // TODO(davi) optimize slack to no hold a copy of the key typedef unordered_map slack_type; @@ -187,28 +195,48 @@ MPH_MAP_METHOD_DECL(void_type, erase)(const key_type& k) { } MPH_MAP_METHOD_DECL(const_iterator, find)(const key_type& k) const { - if (__builtin_expect(!slack_.empty(), 0)) { - auto it = slack_.find(k); - if (it != slack_.end()) return make_iterator(values_.begin() + it->second); + uint32_t h[4]; + auto nest = nests_[index_.hash_vector(k, reinterpret_cast(&h))]; + if (nest != kNestCollision) { + auto vit = values_.begin() + h[nest]; + if (equal_(k, vit->first)) return make_iterator(vit); } - if (__builtin_expect(index_.size() == 0, 0)) return end(); + return slow_find(k); +} + +MPH_MAP_METHOD_DECL(const_iterator, slow_find)(const key_type& k) const { auto id = index_.perfect_hash(k); if (!present_[id]) return end(); - auto it = make_iterator(values_.begin() + id); - if (__builtin_expect(equal_(k, it->first), 1)) return it; + auto vit = values_.begin() + id; + if (equal_(k, vit->first)) return make_iterator(vit); + + if (__builtin_expect(!slack_.empty(), 0)) { + auto sit = slack_.find(k); + if (it != slack_.end()) return make_iterator(values_.begin() + sit->second); + } return end(); } MPH_MAP_METHOD_DECL(iterator, find)(const key_type& k) { - if (__builtin_expect(!slack_.empty(), 0)) { - auto it = slack_.find(k); - if (it != slack_.end()) return make_iterator(values_.begin() + it->second); + uint32_t h[4]; + auto nest = nests_[index_.hash_vector(k, reinterpret_cast(&h))]; + if (nest != kNestCollision) { + auto vit = values_.begin() + h[nest]; + if (equal_(k, vit->first)) return make_iterator(vit); } - if (__builtin_expect(index_.size() == 0, 0)) return end(); + return slow_find(k); +} + +MPH_MAP_METHOD_DECL(iterator, slow_find)(const key_type& k) { auto id = index_.perfect_hash(k); if (!present_[id]) return end(); - auto it = make_iterator(values_.begin() + id); - if (__builtin_expect(equal_(k, it->first), 1)) return it; + auto vit = values_.begin() + id; + if (equal_(k, vit->first)) return make_iterator(vit); + + if (__builtin_expect(!slack_.empty(), 0)) { + auto sit = slack_.find(k); + if (it != slack_.end()) return make_iterator(values_.begin() + sit->second); + } return end(); } diff --git a/cxxmph/seeded_hash.h b/cxxmph/seeded_hash.h index f0bab05..69cb0ac 100644 --- a/cxxmph/seeded_hash.h +++ b/cxxmph/seeded_hash.h @@ -65,9 +65,8 @@ struct Murmur3Fmix64bitsType { } template void hash64(const Key& k, uint32_t* out) const { - uint64_t h = fmix(*reinterpret_cast(&k)); - *reinterpret_cast(out) = h; - *reinterpret_cast(out + 2) = h; + *reinterpret_cast(out) = fmix(k); + *(out + 2) = fmix(*out); } }; @@ -145,9 +144,9 @@ template <> struct seeded_hash > template <> struct seeded_hash > { typedef seeded_hash_function hash_function; }; template <> struct seeded_hash > -{ typedef seeded_hash_function hash_function; }; +{ typedef seeded_hash_function hash_function; }; template <> struct seeded_hash > -{ typedef seeded_hash_function hash_function; }; +{ typedef seeded_hash_function hash_function; }; } // namespace cxxmph