// Copyright 2021, 2022 Peter Dimov. // Copyright 2023 Joaquin M Lopez Munoz. // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt #define _SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING #define _SILENCE_CXX20_CISO646_REMOVED_WARNING #include #include #include #include #ifdef HAVE_ABSEIL # include "absl/container/node_hash_map.h" # include "absl/container/flat_hash_map.h" #endif #ifdef HAVE_ANKERL_UNORDERED_DENSE # include "ankerl/unordered_dense.h" #endif #include #include #include #include #include #include #include #include #include #include using namespace std::chrono_literals; static void print_time( std::chrono::steady_clock::time_point & t1, char const* label, std::size_t s, std::size_t size ) { auto t2 = std::chrono::steady_clock::now(); std::cout << label << ": " << ( t2 - t1 ) / 1ms << " ms (s=" << s << ", size=" << size << ")\n"; t1 = t2; } static std::vector words; static void init_words() { #if SIZE_MAX > UINT32_MAX char const* fn = "enwik9"; // http://mattmahoney.net/dc/textdata #else char const* fn = "enwik8"; // ditto #endif auto t1 = std::chrono::steady_clock::now(); std::ifstream is( fn ); std::string in( std::istreambuf_iterator( is ), std::istreambuf_iterator{} ); boost::regex re( "[a-zA-Z]+"); boost::sregex_token_iterator it( in.begin(), in.end(), re, 0 ), end; words.assign( it, end ); auto t2 = std::chrono::steady_clock::now(); std::cout << fn << ": " << words.size() << " words, " << ( t2 - t1 ) / 1ms << " ms\n\n"; } template BOOST_NOINLINE void test_word_count( Map& map, std::chrono::steady_clock::time_point & t1 ) { std::size_t s = 0; for( auto const& word: words ) { ++map[ word ]; ++s; } print_time( t1, "Word count", s, map.size() ); std::cout << std::endl; } template BOOST_NOINLINE void test_contains( Map& map, std::chrono::steady_clock::time_point & t1 ) { std::size_t s = 0; for( auto const& word: words ) { std::string_view w2( word ); w2.remove_prefix( 1 ); s += map.contains( w2 ); } print_time( t1, "Contains", s, map.size() ); std::cout << std::endl; } template BOOST_NOINLINE void test_count( Map& map, std::chrono::steady_clock::time_point & t1 ) { std::size_t s = 0; for( auto const& word: words ) { std::string_view w2( word ); w2.remove_prefix( 1 ); s += map.count( w2 ); } print_time( t1, "Count", s, map.size() ); std::cout << std::endl; } template BOOST_NOINLINE void test_iteration( Map& map, std::chrono::steady_clock::time_point & t1 ) { std::size_t max = 0; std::string_view word; for( auto const& x: map ) { if( x.second > max ) { word = x.first; max = x.second; } } print_time( t1, "Iterate and find max element", max, map.size() ); std::cout << std::endl; } // counting allocator static std::size_t s_alloc_bytes = 0; static std::size_t s_alloc_count = 0; template struct allocator { using value_type = T; allocator() = default; template allocator( allocator const & ) noexcept { } template bool operator==( allocator const & ) const noexcept { return true; } template bool operator!=( allocator const& ) const noexcept { return false; } T* allocate( std::size_t n ) const { s_alloc_bytes += n * sizeof(T); s_alloc_count++; return std::allocator().allocate( n ); } void deallocate( T* p, std::size_t n ) const noexcept { s_alloc_bytes -= n * sizeof(T); s_alloc_count--; std::allocator().deallocate( p, n ); } }; // struct record { std::string label_; long long time_; std::size_t bytes_; std::size_t count_; }; static std::vector times; template class Map> BOOST_NOINLINE void test( char const* label ) { std::cout << label << ":\n\n"; s_alloc_bytes = 0; s_alloc_count = 0; Map map; auto t0 = std::chrono::steady_clock::now(); auto t1 = t0; test_word_count( map, t1 ); std::cout << "Memory: " << s_alloc_bytes << " bytes in " << s_alloc_count << " allocations\n\n"; record rec = { label, 0, s_alloc_bytes, s_alloc_count }; test_contains( map, t1 ); test_count( map, t1 ); test_iteration( map, t1 ); auto tN = std::chrono::steady_clock::now(); std::cout << "Total: " << ( tN - t0 ) / 1ms << " ms\n\n"; rec.time_ = ( tN - t0 ) / 1ms; times.push_back( rec ); } // aliases using the counting allocator template using allocator_for = ::allocator< std::pair >; template using std_unordered_map = std::unordered_map, std::equal_to, allocator_for>; template using boost_unordered_map = boost::unordered_map, std::equal_to, allocator_for>; template using boost_unordered_node_map = boost::unordered_node_map, std::equal_to, allocator_for>; template using boost_unordered_flat_map = boost::unordered_flat_map, std::equal_to, allocator_for>; #ifdef HAVE_ABSEIL template using absl_node_hash_map = absl::node_hash_map, absl::container_internal::hash_default_eq, allocator_for>; template using absl_flat_hash_map = absl::flat_hash_map, absl::container_internal::hash_default_eq, allocator_for>; #endif #ifdef HAVE_ANKERL_UNORDERED_DENSE template using ankerl_unordered_dense_map = ankerl::unordered_dense::map, std::equal_to, ::allocator< std::pair >>; #endif // fnv1a_hash template struct fnv1a_hash_impl; template<> struct fnv1a_hash_impl<32> { std::size_t operator()( std::string_view const& s ) const { std::size_t h = 0x811C9DC5u; char const * first = s.data(); char const * last = first + s.size(); for( ; first != last; ++first ) { h ^= static_cast( *first ); h *= 0x01000193ul; } return h; } }; template<> struct fnv1a_hash_impl<64> { std::size_t operator()( std::string_view const& s ) const { std::size_t h = 0xCBF29CE484222325ull; char const * first = s.data(); char const * last = first + s.size(); for( ; first != last; ++first ) { h ^= static_cast( *first ); h *= 0x00000100000001B3ull; } return h; } }; struct fnv1a_hash: fnv1a_hash_impl< std::numeric_limits::digits > { using is_avalanching = void; }; template using std_unordered_map_fnv1a = std::unordered_map, allocator_for>; template using boost_unordered_map_fnv1a = boost::unordered_map, allocator_for>; template using boost_unordered_node_map_fnv1a = boost::unordered_node_map, allocator_for>; template using boost_unordered_flat_map_fnv1a = boost::unordered_flat_map, allocator_for>; #ifdef HAVE_ABSEIL template using absl_node_hash_map_fnv1a = absl::node_hash_map, allocator_for>; template using absl_flat_hash_map_fnv1a = absl::flat_hash_map, allocator_for>; #endif #ifdef HAVE_ANKERL_UNORDERED_DENSE template using ankerl_unordered_dense_map_fnv1a = ankerl::unordered_dense::map, ::allocator< std::pair >>; #endif // int main() { init_words(); test( "std::unordered_map" ); test( "boost::unordered_map" ); test( "boost::unordered_node_map" ); test( "boost::unordered_flat_map" ); #ifdef HAVE_ANKERL_UNORDERED_DENSE test( "ankerl::unordered_dense::map" ); #endif #ifdef HAVE_ABSEIL test( "absl::node_hash_map" ); test( "absl::flat_hash_map" ); #endif test( "std::unordered_map, FNV-1a" ); test( "boost::unordered_map, FNV-1a" ); test( "boost::unordered_node_map, FNV-1a" ); test( "boost::unordered_flat_map, FNV-1a" ); #ifdef HAVE_ANKERL_UNORDERED_DENSE test( "ankerl::unordered_dense::map, FNV-1a" ); #endif #ifdef HAVE_ABSEIL test( "absl::node_hash_map, FNV-1a" ); test( "absl::flat_hash_map, FNV-1a" ); #endif std::cout << "---\n\n"; for( auto const& x: times ) { std::cout << std::setw( 38 ) << ( x.label_ + ": " ) << std::setw( 5 ) << x.time_ << " ms, " << std::setw( 9 ) << x.bytes_ << " bytes in " << x.count_ << " allocations\n"; } } #ifdef HAVE_ABSEIL # include "absl/container/internal/raw_hash_set.cc" # include "absl/hash/internal/hash.cc" # include "absl/hash/internal/low_level_hash.cc" # include "absl/hash/internal/city.cc" #endif