From e712c37e9f437fd9667f8a25baaa52d830476980 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sun, 28 Apr 2024 18:44:23 +0200 Subject: [PATCH] added stats to foa::table --- .../unordered/detail/cumulative_stats.hpp | 155 ++++++++++++++++++ include/boost/unordered/detail/foa/core.hpp | 83 ++++++++++ include/boost/unordered/detail/foa/table.hpp | 9 + 3 files changed, 247 insertions(+) create mode 100644 include/boost/unordered/detail/cumulative_stats.hpp diff --git a/include/boost/unordered/detail/cumulative_stats.hpp b/include/boost/unordered/detail/cumulative_stats.hpp new file mode 100644 index 00000000..18f96d39 --- /dev/null +++ b/include/boost/unordered/detail/cumulative_stats.hpp @@ -0,0 +1,155 @@ +/* Copyright 2024 Joaquin M Lopez Munoz. + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * See https://www.boost.org/libs/unordered for library home page. + */ + +#ifndef BOOST_UNORDERED_DETAIL_CUMULATIVE_STATS_HPP +#define BOOST_UNORDERED_DETAIL_CUMULATIVE_STATS_HPP + +#include +#include +#include +#include +#include + +#if defined(BOOST_HAS_THREADS) +#include +#endif + +namespace boost{ +namespace unordered{ +namespace detail{ + +/* Cumulative one-pass calculation of the average, variance and deviation of + * running sequences. + */ + +struct cumulative_stats_summary +{ + double average; + double variance; + double deviation; +}; + +struct cumulative_stats_data +{ + double m=0.0; + double m_prior=0.0; + double s=0.0; +}; + +struct welfords_algorithm /* 0-based */ +{ + template + int operator()(T&& x,cumulative_stats_data& d)const + { + d.m_prior=d.m; + d.m+=(static_cast(x)-d.m)/static_cast(n); + d.s+=(n!=1)* + (static_cast(x)-d.m_prior)*(static_cast(x)-d.m); + + return 0; /* mp11::tuple_transform requires that return type not be void */ + } + + std::size_t n; +}; + +/* Stats calculated jointly for N same-sized sequences to save the space + * for n. + */ + +template +class cumulative_stats +{ +public: + void reset()noexcept{*this=cumulative_stats();} + + template + void add(Ts&&... xs) + { + static_assert( + sizeof...(Ts)==N,"A sample must be provided for each sequence."); + + mp11::tuple_transform( + welfords_algorithm{++n}, + std::forward_as_tuple(std::forward(xs)...), + data); + } + + template + cumulative_stats_summary get_summary()const noexcept + { + double average=data[I].m, + variance=n!=0?data[I].s/static_cast(n):0.0, /* biased */ + deviation=std::sqrt(variance); + return {average,variance,deviation}; + } + +private: + std::size_t n=0; + std::array data; +}; + +#if defined(BOOST_HAS_THREADS) + +template +class concurrent_cumulative_stats:cumulative_stats +{ + using super=cumulative_stats; + using lock_guard=std::lock_guard; + +public: + concurrent_cumulative_stats()noexcept:super{}{} + concurrent_cumulative_stats(const concurrent_cumulative_stats& x)noexcept: + concurrent_cumulative_stats{x,lock_guard{x.mut}}{} + + concurrent_cumulative_stats& + operator=(const concurrent_cumulative_stats& x)noexcept + { + auto x1=x; + lock_guard lck{mut}; + static_cast(*this)=x1; + return *this; + } + + void reset()noexcept + { + lock_guard lck{mut}; + super::reset(); + } + + template + void add(Ts&&... xs) + { + lock_guard lck{mut}; + super::add(std::forward(xs)...); + } + + template + cumulative_stats_summary get_summary()const noexcept + { + lock_guard lck{mut}; + return super::template get_summary(); + } + +private: + concurrent_cumulative_stats(const super& x,lock_guard&&):super{x}{} + + mutable std::mutex mut; +}; + +#else + +template +using concurrent_cumulative_stats=cumulative_stats; + +#endif + +} /* namespace detail */ +} /* namespace unordered */ +} /* namespace boost */ + +#endif diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 9955ca1d..6fcc746a 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -40,6 +40,10 @@ #include #include +#if defined(BOOST_UNORDERED_ENABLE_STATS) +#include +#endif + #if !defined(BOOST_UNORDERED_DISABLE_SSE2) #if defined(BOOST_UNORDERED_ENABLE_SSE2)|| \ defined(__SSE2__)|| \ @@ -864,6 +868,7 @@ struct pow2_quadratic_prober pow2_quadratic_prober(std::size_t pos_):pos{pos_}{} inline std::size_t get()const{return pos;} + inline std::size_t length()const{return step+1;} /* next returns false when the whole array has been traversed, which ends * probing (in practice, full-table probing will only happen with very small @@ -1125,6 +1130,39 @@ struct table_arrays value_type_pointer elements_; }; +#if defined(BOOST_UNORDERED_ENABLE_STATS) +/* stats support */ + +struct table_core_insertion_stats +{ + cumulative_stats_summary probe_length; +}; + +struct table_core_lookup_stats +{ + cumulative_stats_summary probe_length; + cumulative_stats_summary num_comparisons; +}; + +struct table_core_stats +{ + table_core_insertion_stats insertion; + table_core_lookup_stats successful_lookup, + unsuccessful_lookup; +}; + +#define BOOST_UNORDERED_ADD_STATS(stats,args) stats.add args +#define BOOST_UNORDERED_STATS_COUNTER(name) std::size_t name=0 +#define BOOST_UNORDERED_INCREMENT_STATS_COUNTER(name) ++name + +#else + +#define BOOST_UNORDERED_ADD_STATS(stats,args) +#define BOOST_UNORDERED_STATS_COUNTER(name) +#define BOOST_UNORDERED_INCREMENT_STATS_COUNTER(name) + +#endif + struct if_constexpr_void_else{void operator()()const{}}; template @@ -1395,6 +1433,10 @@ public: using locator=table_locator; using arrays_holder_type=arrays_holder; +#if defined(BOOST_UNORDERED_ENABLE_STATS) + using stats=table_core_stats; +#endif + table_core( std::size_t n=default_bucket_count,const Hash& h_=Hash(), const Pred& pred_=Pred(),const Allocator& al_=Allocator()): @@ -1639,6 +1681,7 @@ public: BOOST_FORCEINLINE locator find( const Key& x,std::size_t pos0,std::size_t hash)const { + BOOST_UNORDERED_STATS_COUNTER(num_cmps); prober pb(pos0); do{ auto pos=pb.get(); @@ -1650,18 +1693,25 @@ public: auto p=elements+pos*N; BOOST_UNORDERED_PREFETCH_ELEMENTS(p,N); do{ + BOOST_UNORDERED_INCREMENT_STATS_COUNTER(num_cmps); auto n=unchecked_countr_zero(mask); if(BOOST_LIKELY(bool(pred()(x,key_from(p[n]))))){ + BOOST_UNORDERED_ADD_STATS( + successful_lookup_stats,(pb.length(),num_cmps)); return {pg,n,p+n}; } mask&=mask-1; }while(mask); } if(BOOST_LIKELY(pg->is_not_overflowed(hash))){ + BOOST_UNORDERED_ADD_STATS( + unsuccessful_lookup_stats,(pb.length(),num_cmps)); return {}; } } while(BOOST_LIKELY(pb.next(arrays.groups_size_mask))); + BOOST_UNORDERED_ADD_STATS( + unsuccessful_lookup_stats,(pb.length(),num_cmps)); return {}; } @@ -1746,6 +1796,32 @@ public: rehash(std::size_t(std::ceil(float(n)/mlf))); } +#if defined(BOOST_UNORDERED_ENABLE_STATS) + stats get_stats()const + { + return { + { + insertion_stats.get_summary<0>() + }, + { + successful_lookup_stats.get_summary<0>(), + successful_lookup_stats.get_summary<1>() + }, + { + unsuccessful_lookup_stats.get_summary<0>(), + unsuccessful_lookup_stats.get_summary<1>() + } + }; + } + + void reset_stats() + { + insertion_stats.reset(); + successful_lookup_stats.reset(); + unsuccessful_lookup_stats.reset(); + } +#endif + friend bool operator==(const table_core& x,const table_core& y) { return @@ -1956,6 +2032,12 @@ public: arrays_type arrays; size_ctrl_type size_ctrl; +#if defined(BOOST_UNORDERED_ENABLE_STATS) + concurrent_cumulative_stats<1> insertion_stats; + mutable concurrent_cumulative_stats<2> successful_lookup_stats, + unsuccessful_lookup_stats; +#endif + private: template< typename,typename,template class, @@ -2243,6 +2325,7 @@ private: auto p=arrays_.elements()+pos*N+n; construct_element(p,std::forward(args)...); pg->set(n,hash); + BOOST_UNORDERED_ADD_STATS(insertion_stats,(pb.length())); return {pg,n,p}; } else pg->mark_overflow(hash); diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 87aa0495..2f02a7a4 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -361,6 +361,10 @@ public: const_iterator>::type; using erase_return_type=table_erase_return_type; +#if defined(BOOST_UNORDERED_ENABLE_STATS) + using stats=typename super::stats; +#endif + table( std::size_t n=default_bucket_count,const Hash& h_=Hash(), const Pred& pred_=Pred(),const Allocator& al_=Allocator()): @@ -542,6 +546,11 @@ public: using super::rehash; using super::reserve; +#if defined(BOOST_UNORDERED_ENABLE_STATS) + using super::get_stats; + using super::reset_stats; +#endif + template friend std::size_t erase_if(table& x,Predicate& pr) {