1 // Copyright 2021 Peter Dimov.
2 // Distributed under the Boost Software License, Version 1.0.
3 // https://www.boost.org/LICENSE_1_0.txt
5 #define _SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING
7 #include <boost/unordered_map.hpp>
8 #include <boost/multi_index_container.hpp>
9 #include <boost/multi_index/hashed_index.hpp>
10 #include <boost/multi_index/member.hpp>
11 #include <boost/core/detail/splitmix64.hpp>
12 #include <boost/config.hpp>
14 # include "absl/container/node_hash_map.h"
15 # include "absl/container/flat_hash_map.h"
17 #include <unordered_map>
25 using namespace std::chrono_literals
;
27 static void print_time( std::chrono::steady_clock::time_point
& t1
, char const* label
, std::uint32_t s
, std::size_t size
)
29 auto t2
= std::chrono::steady_clock::now();
31 std::cout
<< label
<< ": " << ( t2
- t1
) / 1ms
<< " ms (s=" << s
<< ", size=" << size
<< ")\n";
36 constexpr unsigned N
= 2'000'000;
39 static std::vector
<std::string
> indices1
, indices2
;
41 static std::string
make_index( unsigned x
)
44 std::snprintf( buffer
, sizeof(buffer
), "pfx_%u_sfx", x
);
49 static std::string
make_random_index( unsigned x
)
52 std::snprintf( buffer
, sizeof(buffer
), "pfx_%0*d_%u_sfx", x
% 8 + 1, 0, x
);
57 static void init_indices()
59 indices1
.reserve( N
*2+1 );
60 indices1
.push_back( make_index( 0 ) );
62 for( unsigned i
= 1; i
<= N
*2; ++i
)
64 indices1
.push_back( make_index( i
) );
67 indices2
.reserve( N
*2+1 );
68 indices2
.push_back( make_index( 0 ) );
71 boost::detail::splitmix64 rng
;
73 for( unsigned i
= 1; i
<= N
*2; ++i
)
75 indices2
.push_back( make_random_index( static_cast<std::uint32_t>( rng() ) ) );
80 template<class Map
> BOOST_NOINLINE
void test_insert( Map
& map
, std::chrono::steady_clock::time_point
& t1
)
82 for( unsigned i
= 1; i
<= N
; ++i
)
84 map
.insert( { indices1
[ i
], i
} );
87 print_time( t1
, "Consecutive insert", 0, map
.size() );
89 for( unsigned i
= 1; i
<= N
; ++i
)
91 map
.insert( { indices2
[ i
], i
} );
94 print_time( t1
, "Random insert", 0, map
.size() );
96 std::cout
<< std::endl
;
99 template<class Map
> BOOST_NOINLINE
void test_lookup( Map
& map
, std::chrono::steady_clock::time_point
& t1
)
105 for( int j
= 0; j
< K
; ++j
)
107 for( unsigned i
= 1; i
<= N
* 2; ++i
)
109 auto it
= map
.find( indices1
[ i
] );
110 if( it
!= map
.end() ) s
+= it
->second
;
114 print_time( t1
, "Consecutive lookup", s
, map
.size() );
118 for( int j
= 0; j
< K
; ++j
)
120 for( unsigned i
= 1; i
<= N
* 2; ++i
)
122 auto it
= map
.find( indices2
[ i
] );
123 if( it
!= map
.end() ) s
+= it
->second
;
127 print_time( t1
, "Random lookup", s
, map
.size() );
129 std::cout
<< std::endl
;
132 template<class Map
> BOOST_NOINLINE
void test_iteration( Map
& map
, std::chrono::steady_clock::time_point
& t1
)
134 auto it
= map
.begin();
136 while( it
!= map
.end() )
148 print_time( t1
, "Iterate and erase odd elements", 0, map
.size() );
150 std::cout
<< std::endl
;
153 template<class Map
> BOOST_NOINLINE
void test_erase( Map
& map
, std::chrono::steady_clock::time_point
& t1
)
155 for( unsigned i
= 1; i
<= N
; ++i
)
157 map
.erase( indices1
[ i
] );
160 print_time( t1
, "Consecutive erase", 0, map
.size() );
163 boost::detail::splitmix64 rng
;
165 for( unsigned i
= 1; i
<= N
; ++i
)
167 map
.erase( indices2
[ i
] );
171 print_time( t1
, "Random erase", 0, map
.size() );
173 std::cout
<< std::endl
;
176 // counting allocator
178 static std::size_t s_alloc_bytes
= 0;
179 static std::size_t s_alloc_count
= 0;
181 template<class T
> struct allocator
183 using value_type
= T
;
185 allocator() = default;
187 template<class U
> allocator( allocator
<U
> const & ) noexcept
191 template<class U
> bool operator==( allocator
<U
> const & ) const noexcept
196 template<class U
> bool operator!=( allocator
<U
> const& ) const noexcept
201 T
* allocate( std::size_t n
) const
203 s_alloc_bytes
+= n
* sizeof(T
);
206 return std::allocator
<T
>().allocate( n
);
209 void deallocate( T
* p
, std::size_t n
) const noexcept
211 s_alloc_bytes
-= n
* sizeof(T
);
214 std::allocator
<T
>().deallocate( p
, n
);
228 static std::vector
<record
> times
;
230 template<template<class...> class Map
> BOOST_NOINLINE
void test( char const* label
)
232 std::cout
<< label
<< ":\n\n";
237 Map
<std::string
, std::uint32_t> map
;
239 auto t0
= std::chrono::steady_clock::now();
242 test_insert( map
, t1
);
244 std::cout
<< "Memory: " << s_alloc_bytes
<< " bytes in " << s_alloc_count
<< " allocations\n\n";
246 record rec
= { label
, 0, s_alloc_bytes
, s_alloc_count
};
248 test_lookup( map
, t1
);
249 test_iteration( map
, t1
);
250 test_lookup( map
, t1
);
251 test_erase( map
, t1
);
253 auto tN
= std::chrono::steady_clock::now();
254 std::cout
<< "Total: " << ( tN
- t0
) / 1ms
<< " ms\n\n";
256 rec
.time_
= ( tN
- t0
) / 1ms
;
257 times
.push_back( rec
);
260 // multi_index emulation of unordered_map
262 template<class K
, class V
> struct pair
268 using namespace boost::multi_index
;
270 template<class K
, class V
> using multi_index_map
= multi_index_container
<
273 hashed_unique
< member
<pair
<K
, V
>, K
, &pair
<K
, V
>::first
> >
275 ::allocator
< pair
<K
, V
> >
278 // aliases using the counting allocator
280 template<class K
, class V
> using allocator_for
= ::allocator
< std::pair
<K
const, V
> >;
282 template<class K
, class V
> using std_unordered_map
=
283 std::unordered_map
<K
, V
, std::hash
<K
>, std::equal_to
<K
>, allocator_for
<K
, V
>>;
285 template<class K
, class V
> using boost_unordered_map
=
286 boost::unordered_map
<K
, V
, boost::hash
<K
>, std::equal_to
<K
>, allocator_for
<K
, V
>>;
290 template<class K
, class V
> using absl_node_hash_map
=
291 absl::node_hash_map
<K
, V
, absl::container_internal::hash_default_hash
<K
>, absl::container_internal::hash_default_eq
<K
>, allocator_for
<K
, V
>>;
293 template<class K
, class V
> using absl_flat_hash_map
=
294 absl::flat_hash_map
<K
, V
, absl::container_internal::hash_default_hash
<K
>, absl::container_internal::hash_default_eq
<K
>, allocator_for
<K
, V
>>;
300 template<int Bits
> struct fnv1a_hash_impl
;
302 template<> struct fnv1a_hash_impl
<32>
304 std::size_t operator()( std::string
const& s
) const
306 std::size_t h
= 0x811C9DC5u
;
308 char const * first
= s
.data();
309 char const * last
= first
+ s
.size();
311 for( ; first
!= last
; ++first
)
313 h
^= static_cast<unsigned char>( *first
);
321 template<> struct fnv1a_hash_impl
<64>
323 std::size_t operator()( std::string
const& s
) const
325 std::size_t h
= 0xCBF29CE484222325ull
;
327 char const * first
= s
.data();
328 char const * last
= first
+ s
.size();
330 for( ; first
!= last
; ++first
)
332 h
^= static_cast<unsigned char>( *first
);
333 h
*= 0x00000100000001B3ull
;
340 struct fnv1a_hash
: fnv1a_hash_impl
< std::numeric_limits
<std::size_t>::digits
> {};
342 template<class K
, class V
> using std_unordered_map_fnv1a
=
343 std::unordered_map
<K
, V
, fnv1a_hash
, std::equal_to
<K
>, allocator_for
<K
, V
>>;
345 template<class K
, class V
> using boost_unordered_map_fnv1a
=
346 boost::unordered_map
<K
, V
, fnv1a_hash
, std::equal_to
<K
>, allocator_for
<K
, V
>>;
348 template<class K
, class V
> using multi_index_map_fnv1a
= multi_index_container
<
351 hashed_unique
< member
<pair
<K
, V
>, K
, &pair
<K
, V
>::first
>, fnv1a_hash
>
353 ::allocator
< pair
<K
, V
> >
358 template<class K
, class V
> using absl_node_hash_map_fnv1a
=
359 absl::node_hash_map
<K
, V
, fnv1a_hash
, absl::container_internal::hash_default_eq
<K
>, allocator_for
<K
, V
>>;
361 template<class K
, class V
> using absl_flat_hash_map_fnv1a
=
362 absl::flat_hash_map
<K
, V
, fnv1a_hash
, absl::container_internal::hash_default_eq
<K
>, allocator_for
<K
, V
>>;
374 test
<std_unordered_map
>( "std::unordered_map" );
375 test
<boost_unordered_map
>( "boost::unordered_map" );
376 test
<multi_index_map
>( "multi_index_map" );
380 test
<absl_node_hash_map
>( "absl::node_hash_map" );
381 test
<absl_flat_hash_map
>( "absl::flat_hash_map" );
387 test
<std_unordered_map_fnv1a
>( "std::unordered_map, FNV-1a" );
388 test
<boost_unordered_map_fnv1a
>( "boost::unordered_map, FNV-1a" );
389 test
<multi_index_map_fnv1a
>( "multi_index_map, FNV-1a" );
393 test
<absl_node_hash_map_fnv1a
>( "absl::node_hash_map, FNV-1a" );
394 test
<absl_flat_hash_map_fnv1a
>( "absl::flat_hash_map, FNV-1a" );
398 std::cout
<< "---\n\n";
400 for( auto const& x
: times
)
402 std::cout
<< std::setw( 30 ) << ( x
.label_
+ ": " ) << std::setw( 5 ) << x
.time_
<< " ms, " << std::setw( 9 ) << x
.bytes_
<< " bytes in " << x
.count_
<< " allocations\n";
407 # include "absl/container/internal/raw_hash_set.cc"
408 # include "absl/hash/internal/hash.cc"
409 # include "absl/hash/internal/low_level_hash.cc"
410 # include "absl/hash/internal/city.cc"