From 120fe90934995622d78df23fda3cd4846edb5f16 Mon Sep 17 00:00:00 2001 From: Peter Dimov Date: Wed, 26 Oct 2022 21:38:09 +0300 Subject: [PATCH] Add benchmark/uuid.cpp --- benchmark/uuid.cpp | 393 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 393 insertions(+) create mode 100644 benchmark/uuid.cpp diff --git a/benchmark/uuid.cpp b/benchmark/uuid.cpp new file mode 100644 index 00000000..1da32712 --- /dev/null +++ b/benchmark/uuid.cpp @@ -0,0 +1,393 @@ +// Copyright 2021, 2022 Peter Dimov. +// 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_ABSEIL +# include "absl/container/node_hash_map.h" +# include "absl/container/flat_hash_map.h" +#endif +#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::uint64_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; +} + +constexpr unsigned N = 2'000'000; +constexpr int K = 10; + +struct uuid +{ + unsigned char data[ 16 ]; + + uuid(): data() + { + } + + uuid( std::uint64_t high, std::uint64_t low ) noexcept + { + boost::endian::store_little_u64( data + 0, low ); + boost::endian::store_little_u64( data + 8, high ); + } + + inline friend std::size_t hash_value( uuid const& u ) noexcept + { + return boost::hash_value( u.data ); + } + + inline friend bool operator==( uuid const& u1, uuid const& u2 ) noexcept + { + return std::equal( u1.data + 0, u1.data + 16, u2.data ); + } +}; + +namespace std +{ + +template<> struct hash< ::uuid > +{ + std::size_t operator()( uuid const& u ) const noexcept + { + return boost::hash_value( u.data ); + } +}; + +} // namespace std + +static std::vector< uuid > indices1, indices2, indices3; + +static void init_indices() +{ + indices1.push_back( {} ); + + for( unsigned i = 1; i <= N*2; ++i ) + { + indices1.push_back( { i, 0 } ); + } + + indices2.push_back( {} ); + + { + boost::detail::splitmix64 rng; + + for( unsigned i = 1; i <= N*2; ++i ) + { + indices2.push_back( { rng(), rng() } ); + } + } + + indices3.push_back( {} ); + + for( unsigned i = 1; i <= N*2; ++i ) + { + uuid k( i, 0 ); + std::reverse( k.data + 0, k.data + 16 ); + + indices3.push_back( k ); + } +} + +template BOOST_NOINLINE void test_insert( Map& map, std::chrono::steady_clock::time_point & t1 ) +{ + for( unsigned i = 1; i <= N; ++i ) + { + map.insert( { indices1[ i ], i } ); + } + + print_time( t1, "Consecutive insert", 0, map.size() ); + + for( unsigned i = 1; i <= N; ++i ) + { + map.insert( { indices2[ i ], i } ); + } + + print_time( t1, "Random insert", 0, map.size() ); + + for( unsigned i = 1; i <= N; ++i ) + { + map.insert( { indices3[ i ], i } ); + } + + print_time( t1, "Consecutive reversed insert", 0, map.size() ); + + std::cout << std::endl; +} + +template BOOST_NOINLINE void test_lookup( Map& map, std::chrono::steady_clock::time_point & t1 ) +{ + std::uint64_t s; + + s = 0; + + for( int j = 0; j < K; ++j ) + { + for( unsigned i = 1; i <= N * 2; ++i ) + { + auto it = map.find( indices1[ i ] ); + if( it != map.end() ) s += it->second; + } + } + + print_time( t1, "Consecutive lookup", s, map.size() ); + + s = 0; + + for( int j = 0; j < K; ++j ) + { + for( unsigned i = 1; i <= N * 2; ++i ) + { + auto it = map.find( indices2[ i ] ); + if( it != map.end() ) s += it->second; + } + } + + print_time( t1, "Random lookup", s, map.size() ); + + s = 0; + + for( int j = 0; j < K; ++j ) + { + for( unsigned i = 1; i <= N * 2; ++i ) + { + auto it = map.find( indices3[ i ] ); + if( it != map.end() ) s += it->second; + } + } + + print_time( t1, "Consecutive reversed lookup", s, map.size() ); + + std::cout << std::endl; +} + +template BOOST_NOINLINE void test_iteration( Map& map, std::chrono::steady_clock::time_point & t1 ) +{ + auto it = map.begin(); + + while( it != map.end() ) + { + if( it->second & 1 ) + { + if constexpr( std::is_void_v< decltype( map.erase( it ) ) > ) + { + map.erase( it++ ); + } + else + { + it = map.erase( it ); + } + } + else + { + ++it; + } + } + + print_time( t1, "Iterate and erase odd elements", 0, map.size() ); + + std::cout << std::endl; +} + +template BOOST_NOINLINE void test_erase( Map& map, std::chrono::steady_clock::time_point & t1 ) +{ + for( unsigned i = 1; i <= N; ++i ) + { + map.erase( indices1[ i ] ); + } + + print_time( t1, "Consecutive erase", 0, map.size() ); + + for( unsigned i = 1; i <= N; ++i ) + { + map.erase( indices2[ i ] ); + } + + print_time( t1, "Random erase", 0, map.size() ); + + for( unsigned i = 1; i <= N; ++i ) + { + map.erase( indices3[ i ] ); + } + + print_time( t1, "Consecutive reversed erase", 0, 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_insert( 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_lookup( map, t1 ); + test_iteration( map, t1 ); + test_lookup( map, t1 ); + test_erase( 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 ); +} + +// multi_index emulation of unordered_map + +template struct pair +{ + K first; + mutable V second; +}; + +using namespace boost::multi_index; + +template using multi_index_map = multi_index_container< + pair, + indexed_by< + hashed_unique< member, K, &pair::first> > + >, + ::allocator< pair > +>; + +// 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_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 + +int main() +{ + init_indices(); + + test( "std::unordered_map" ); + test( "boost::unordered_map" ); + test( "boost::unordered_flat_map" ); + test( "multi_index_map" ); + +#ifdef HAVE_ABSEIL + + test( "absl::node_hash_map" ); + test( "absl::flat_hash_map" ); + +#endif + + std::cout << "---\n\n"; + + for( auto const& x: times ) + { + std::cout << std::setw( 27 ) << ( 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