diff --git a/bench/bench_deque.cpp b/bench/bench_deque.cpp index 40d3d54..dfc26d1 100644 --- a/bench/bench_deque.cpp +++ b/bench/bench_deque.cpp @@ -21,12 +21,13 @@ #include "bench_vector_common.hpp" template -void run_containers(unsigned numit, unsigned numele, bool bp) +void run_containers(runner& r) { - vector_test_template< std::deque >, Operation >(numit, numele, "std::deque ", bp); - vector_test_template< bc::deque >, Operation >(numit, numele, "deque ", bp); - vector_test_template< bc::deque, - typename bc::deque_options >::type >, Operation >(numit, numele, "deque(reserv) ", bp); + //First registered container is the baseline (denominator). + r.template add< std::deque > >("std::deque"); + r.template add< bc::deque > >("deque"); + r.template add< bc::deque, + typename bc::deque_options >::type> >("deque(resv)"); } int main() diff --git a/bench/bench_devector.cpp b/bench/bench_devector.cpp index 431e46d..aeb1bd1 100644 --- a/bench/bench_devector.cpp +++ b/bench/bench_devector.cpp @@ -21,11 +21,12 @@ #include "bench_vector_common.hpp" template -void run_containers(unsigned numit, unsigned numele, bool bp) +void run_containers(runner& r) { - vector_test_template< bc::devector >, Operation >(numit, numele, "devector ", bp); - vector_test_template< bc::deque >, Operation >(numit, numele, "deque ", bp); - vector_test_template< bc::vector >, Operation >(numit, numele, "vector ", bp); + //First registered container is the baseline (denominator). + r.template add< bc::vector > >("vector"); + r.template add< bc::deque > >("deque"); + r.template add< bc::devector > >("devector"); } int main() diff --git a/bench/bench_segtor.cpp b/bench/bench_segtor.cpp index 1444eee..19147fe 100644 --- a/bench/bench_segtor.cpp +++ b/bench/bench_segtor.cpp @@ -21,14 +21,15 @@ #include "bench_vector_common.hpp" template -void run_containers(unsigned numit, unsigned numele, bool bp) +void run_containers(runner& r) { - vector_test_template< bc::deque >, Operation >(numit, numele, "deque ", bp); - vector_test_template< bc::deque, - typename bc::deque_options >::type >, Operation >(numit, numele, "deque(reserv) ", bp); - vector_test_template< bc::segtor >, Operation >(numit, numele, "segtor ", bp); - vector_test_template< bc::segtor, - typename bc::segtor_options >::type >, Operation >(numit, numele, "segtor(reserv) ", bp); + //First registered container is the baseline (denominator). + r.template add< bc::deque > >("deque"); + r.template add< bc::deque, + typename bc::deque_options >::type> >("deque(resv)"); + r.template add< bc::segtor > >("segtor"); + r.template add< bc::segtor, + typename bc::segtor_options >::type> >("segtor(resv)"); } int main() diff --git a/bench/bench_small_vector.cpp b/bench/bench_small_vector.cpp index de851ad..d5a023a 100644 --- a/bench/bench_small_vector.cpp +++ b/bench/bench_small_vector.cpp @@ -21,11 +21,12 @@ #include "bench_vector_common.hpp" template -void run_containers(unsigned numit, unsigned numele, bool bp) +void run_containers(runner& r) { - vector_test_template< bc::small_vector >, Operation >(numit, numele, "small_vector ", bp); - vector_test_template< std::vector >, Operation >(numit, numele, "std::vector ", bp); - vector_test_template< bc::vector >, Operation >(numit, numele, "vector ", bp); + //First registered container is the baseline (denominator). + r.template add< std::vector > >("std::vector"); + r.template add< bc::vector > >("vector"); + r.template add< bc::small_vector > >("small_vector"); } int main() diff --git a/bench/bench_static_vector.cpp b/bench/bench_static_vector.cpp index 0c93552..eb9c1cc 100644 --- a/bench/bench_static_vector.cpp +++ b/bench/bench_static_vector.cpp @@ -21,13 +21,14 @@ #include "bench_vector_common.hpp" template -void run_containers(unsigned numit, unsigned numele, bool bp) +void run_containers(runner& r) { + //First registered container is the baseline (denominator). + r.template add< std::vector > >("std::vector"); + r.template add< bc::vector > >("vector"); //static_vector has a fixed capacity, so it must be sized for the largest //element count exercised by the harness. - vector_test_template< bc::static_vector, Operation >(numit, numele, "static_vector ", bp); - vector_test_template< std::vector >, Operation >(numit, numele, "std::vector ", bp); - vector_test_template< bc::vector >, Operation >(numit, numele, "vector ", bp); + r.template add< bc::static_vector >("static_vector"); } int main() diff --git a/bench/bench_vector.cpp b/bench/bench_vector.cpp index ff6acbe..4784a09 100644 --- a/bench/bench_vector.cpp +++ b/bench/bench_vector.cpp @@ -19,10 +19,11 @@ #include "bench_vector_common.hpp" template -void run_containers(unsigned numit, unsigned numele, bool bp) +void run_containers(runner& r) { - vector_test_template< std::vector >, Operation >(numit, numele, "std::vector ", bp); - vector_test_template< bc::vector >, Operation >(numit, numele, "vector ", bp); + //First registered container is the baseline (denominator). + r.template add< std::vector > >("std::vector"); + r.template add< bc::vector > >("vector"); } int main() diff --git a/bench/bench_vector_common.hpp b/bench/bench_vector_common.hpp index eeab573..f2336e3 100644 --- a/bench/bench_vector_common.hpp +++ b/bench/bench_vector_common.hpp @@ -8,16 +8,29 @@ // ////////////////////////////////////////////////////////////////////////////// // -// Shared harness for the sequence-container insertion benchmarks. A runner -// includes this header, defines the run_containers() customization point with -// the concrete containers it wants to compare, and calls test_vectors(): +// Shared harness for the sequence-container insertion benchmarks. +// +// Design: +// - Auto-scaling measurement: each timing repeats the build until a minimum +// wall-clock budget elapses, runs several trials and discards the slowest +// quartile (no hand-tuned iteration counts). +// - Dead-store-elimination barriers: clobber()/escape() (bench_utils.hpp) wrap +// the measured region so the optimizer cannot delete the work. +// - Ratio output: every container is timed and reported as a num/den ratio +// against the first container the runner registers (the baseline), with a +// per-column geometric mean. A compact table is printed per element type and +// prereserve setting. +// +// A runner includes this header, implements the run_containers() customization +// point registering the containers it wants to compare (the FIRST one is the +// baseline/denominator) and calls test_vectors(): // // #include "bench_vector_common.hpp" // template -// void run_containers(unsigned numit, unsigned numele, bool bp) +// void run_containers(runner& r) // { -// vector_test_template< std::vector, Operation >(numit, numele, "std::vector", bp); -// vector_test_template< bc::vector, Operation >(numit, numele, "vector", bp); +// r.template add< std::vector >("std::vector"); //baseline +// r.template add< bc::vector >("vector"); // } // int main(){ test_vectors(); return 0; } // @@ -29,11 +42,18 @@ #include #include -#include -#include //std::cout, std::endl +#include +#include #include +#include +#include +#include +#include #include +#include + #include +#include "bench_utils.hpp" //clobber(), escape() #if defined(BOOST_GCC) && (BOOST_GCC >= 40600) #pragma GCC diagnostic push @@ -68,101 +88,94 @@ #pragma GCC diagnostic pop #endif -using boost::move_detail::cpu_timer; -using boost::move_detail::cpu_times; -using boost::move_detail::nanosecond_type; - namespace bc = boost::container; +/////////////////////////////////////////////////////////////////////////////// +// Benchmark configuration knobs. +// +// Container sizes are swept as 10^min_size_exp .. 10^max_size_exp. The defaults +// follow the same NDEBUG / LONG_BENCH profile idea used by bench_node_cont.hpp. +// +// NOTE: fixed-capacity containers (e.g. static_vector) are sized with +// bench_max_numele, which is derived from max_size_exp, so the two stay in sync. +/////////////////////////////////////////////////////////////////////////////// +struct bench_vector_defaults +{ + typedef boost::move_detail::nanosecond_type nanosecond_type; + +#if defined(LONG_BENCH) + static const std::size_t min_size_exp = 2; + static const std::size_t max_size_exp = 4; + static const std::size_t num_trials = 8; + //50 ms per trial + static const nanosecond_type min_time_per_trial = nanosecond_type(50) * 1000000; +#elif defined(NDEBUG) + static const std::size_t min_size_exp = 2; + static const std::size_t max_size_exp = 4; + static const std::size_t num_trials = 4; + //20 ms per trial + static const nanosecond_type min_time_per_trial = nanosecond_type(20) * 1000000; +#else + static const std::size_t min_size_exp = 2; + static const std::size_t max_size_exp = 3; + static const std::size_t num_trials = 1; + static const nanosecond_type min_time_per_trial = 0; +#endif + + //Which prereserve passes to run (replaces the old RESERVE_STRATEGY macros). + static const bool run_noreserve = true; + static const bool run_prereserve = true; + + //Range length used by the *_range / *_repeated operations, expressed as a + //divisor of the final container size: range = max(1, n / range_divisor). + //A divisor of 100 means each range insertion is 1% of the final size. + static const std::size_t range_divisor = 100; +}; + +//Range length for a given final size n, following the bench_vector_defaults +//policy (fixed range_size, or proportional via range_divisor). +inline std::size_t bench_range_size(std::size_t n) +{ + //Guard against a zero divisor (avoids UB and the compile-time div-by-zero + //diagnostic should the config ever be set to 0). + const std::size_t div = + bench_vector_defaults::range_divisor ? bench_vector_defaults::range_divisor : std::size_t(1); + const std::size_t r = n / div; + return r ? r : std::size_t(1); +} + +//Human-readable description of the range policy, e.g. "n/100". +inline std::string bench_range_label() +{ + std::ostringstream o; + o << "n/" << bench_vector_defaults::range_divisor; + return o.str(); +} + +//Runtime 10^e (e is small here), used where e is only known at run time. +inline BOOST_CONSTEXPR std::size_t bench_pow10(std::size_t e) +{ return e ? std::size_t(10) * bench_pow10(e - 1u) : std::size_t(1); } + +//Compile-time 10^E as an integral constant expression. Unlike bench_pow10 +//above this works in C++03 too (where a function call is not a constant +//expression), so bench_max_numele is usable as static_vector's capacity. +template +struct bench_pow10_c +{ static const std::size_t value = std::size_t(10) * bench_pow10_c::value; }; + +template<> +struct bench_pow10_c<0u> +{ static const std::size_t value = std::size_t(1); }; + //Largest element count exercised by the harness. Fixed-capacity containers //(e.g. static_vector) must be sized at least this big. -static const std::size_t bench_max_numele = 10000; - -class MyInt -{ - int int_; - - public: - inline explicit MyInt(int i = 0) - : int_(i) - {} - - inline MyInt(const MyInt &other) - : int_(other.int_) - {} - - inline MyInt & operator=(const MyInt &other) - { - int_ = other.int_; - return *this; - } - - inline ~MyInt() - { - int_ = 0; - } -}; - -class MyFatInt -{ - int int0_; - int int1_; - int int2_; - int int3_; - int int4_; - int int5_; - int int6_; - int int7_; - - public: - inline explicit MyFatInt(int i = 0) - : int0_(i++) - , int1_(i++) - , int2_(i++) - , int3_(i++) - , int4_(i++) - , int5_(i++) - , int6_(i++) - , int7_(i++) - {} - - inline MyFatInt(const MyFatInt &other) - : int0_(other.int0_) - , int1_(other.int1_) - , int2_(other.int2_) - , int3_(other.int3_) - , int4_(other.int4_) - , int5_(other.int5_) - , int6_(other.int6_) - , int7_(other.int7_) - {} - - inline MyFatInt & operator=(const MyFatInt &other) - { - int0_ = other.int0_; - int1_ = other.int1_; - int2_ = other.int2_; - int3_ = other.int3_; - int4_ = other.int4_; - int5_ = other.int5_; - int6_ = other.int6_; - int7_ = other.int7_; - return *this; - } - - inline ~MyFatInt() - { - int0_ = 0u; - int1_ = 0u; - int2_ = 0u; - int3_ = 0u; - int4_ = 0u; - int5_ = 0u; - int6_ = 0u; - int7_ = 0u; - } -}; +BOOST_CONSTEXPR_OR_CONST std::size_t bench_max_numele = + bench_pow10_c::value; +/////////////////////////////////////////////////////////////////////////////// +// reserve()/capacity() abstraction (some containers offer reserve_back instead, +// some offer neither). +/////////////////////////////////////////////////////////////////////////////// template struct capacity_wrapper_impl { @@ -173,7 +186,6 @@ struct capacity_wrapper_impl { } }; - template struct capacity_wrapper_impl { @@ -203,105 +215,148 @@ struct capacity_wrapper > {}; - -const std::size_t RangeSize = 8; - +/////////////////////////////////////////////////////////////////////////////// +// Operations. Each functor exposes a uniform interface: +// explicit Op(std::size_t n): n is the final container size; range-based ops +// use it to size their range (see bench_range_size). +// capacity_multiplier(): elements inserted per operator() call. +// operator()(C&, int): one insertion step. +// name(): short label for the report. +// +// The range-based operations keep a runtime buffer (their range length is no +// longer a compile-time constant), so they are constructed once per measured +// build, outside the timed region. +/////////////////////////////////////////////////////////////////////////////// template struct insert_end_range { - static inline std::size_t capacity_multiplier() - { return RangeSize; } + explicit insert_end_range(std::size_t n) + : range_(bench_range_size(n)), a_(range_, IntType(0)) + {} + + std::size_t capacity_multiplier() const + { return range_; } template BOOST_CONTAINER_FORCEINLINE void operator()(C &c, int) - { c.insert(c.end(), &a[0], &a[0]+RangeSize); } + { c.insert(c.end(), &a_[0], &a_[0]+range_); } - const char *name() const - { return "insert_end_range(8)"; } + std::string name() const + { return "insert_end_range(" + bench_range_label() + ")"; } - IntType a[RangeSize]; + std::size_t range_; + std::vector a_; }; template struct insert_end_repeated { - static inline std::size_t capacity_multiplier() - { return RangeSize; } + explicit insert_end_repeated(std::size_t n) + : range_(bench_range_size(n)) + {} + + std::size_t capacity_multiplier() const + { return range_; } template BOOST_CONTAINER_FORCEINLINE void operator()(C &c, int i) - { c.insert(c.end(), RangeSize, IntType(i)); } + { c.insert(c.end(), range_, IntType(i)); } - inline const char *name() const - { return "insert_end_repeated(8)"; } + std::string name() const + { return "insert_end_repeated(" + bench_range_label() + ")"; } - IntType a[RangeSize]; + std::size_t range_; }; template struct push_back { - static inline std::size_t capacity_multiplier() + explicit push_back(std::size_t) {} + + std::size_t capacity_multiplier() const { return 1; } template BOOST_CONTAINER_FORCEINLINE void operator()(C &c, int i) { c.push_back(IntType(i)); } - inline const char *name() const + std::string name() const { return "push_back"; } }; template struct emplace_back { - static inline std::size_t capacity_multiplier() + explicit emplace_back(std::size_t) {} + + std::size_t capacity_multiplier() const { return 1; } template BOOST_CONTAINER_FORCEINLINE void operator()(C &c, int i) { c.emplace_back(IntType(i)); } - inline const char *name() const + std::string name() const { return "emplace_back"; } }; template struct insert_near_end_repeated { - static inline std::size_t capacity_multiplier() - { return RangeSize; } + explicit insert_near_end_repeated(std::size_t n) + : range_(bench_range_size(n)) + {} + + std::size_t capacity_multiplier() const + { return range_; } template BOOST_CONTAINER_FORCEINLINE void operator()(C &c, int i) - { c.insert(c.size() >= 4*RangeSize ? c.end()-2*RangeSize : c.end(), RangeSize, IntType(i)); } + { + c.insert(c.size() >= 4*range_ + ? c.end() - static_cast(2*range_) + : c.end(), + range_, IntType(i)); + } - inline const char *name() const - { return "insert_near_end_repeated(8)"; } + std::string name() const + { return "insert_near_end_repeated(" + bench_range_label() + ")"; } + + std::size_t range_; }; template struct insert_near_end_range { - static inline std::size_t capacity_multiplier() - { return RangeSize; } + explicit insert_near_end_range(std::size_t n) + : range_(bench_range_size(n)), a_(range_, IntType(0)) + {} + + std::size_t capacity_multiplier() const + { return range_; } template BOOST_CONTAINER_FORCEINLINE void operator()(C &c, int) { - c.insert(c.size() >= 4*RangeSize ? c.end()-2*RangeSize : c.end(), &a[0], &a[0]+RangeSize); + c.insert(c.size() >= 4*range_ + ? c.end() - static_cast(2*range_) + : c.end(), + &a_[0], &a_[0]+range_); } - inline const char *name() const - { return "insert_near_end_range(8)"; } + std::string name() const + { return "insert_near_end_range(" + bench_range_label() + ")"; } - IntType a[RangeSize]; + std::size_t range_; + std::vector a_; }; template struct insert_near_end { - static inline std::size_t capacity_multiplier() + explicit insert_near_end(std::size_t) {} + + std::size_t capacity_multiplier() const { return 1; } template @@ -313,17 +368,17 @@ struct insert_near_end c.insert(it, IntType(i)); } - inline const char *name() const + std::string name() const { return "insert_near_end"; } }; template struct emplace_near_end { - static inline std::size_t capacity_multiplier() - { - return 1; - } + explicit emplace_near_end(std::size_t) {} + + std::size_t capacity_multiplier() const + { return 1; } template BOOST_CONTAINER_FORCEINLINE void operator()(C& c, int i) @@ -334,214 +389,349 @@ struct emplace_near_end c.emplace(it, IntType(i)); } - inline const char* name() const - { - return "emplace_near_end"; - } + std::string name() const + { return "emplace_near_end"; } }; -template -void vector_test_template(std::size_t num_iterations, std::size_t num_elements, const char *cont_name, bool prereserve = true) +/////////////////////////////////////////////////////////////////////////////// +// Auto-scaling measurement with DSE barriers. +// +// A "measured run" builds one container of num_elements via repeated operations. +// Construction, the optional reserve and the destruction are excluded from the +// timing using the pause/resume mechanism (the same approach as +// bench_node_cont.hpp): the measured wall-clock is total - excluded. +/////////////////////////////////////////////////////////////////////////////// +namespace bench_vector_detail { + +typedef boost::move_detail::nanosecond_type nsec_t; + +//measure_start is advanced forward by resume_timing() so that +//(now - measure_start) yields measured (non-paused) time. +static nsec_t measure_start = 0; +static nsec_t measure_pause = 0; + +BOOST_CONTAINER_FORCEINLINE void pause_timing() +{ measure_pause = boost::move_detail::nsec_clock(); } + +BOOST_CONTAINER_FORCEINLINE void resume_timing() +{ measure_start += boost::move_detail::nsec_clock() - measure_pause; } + +template +BOOST_NOINLINE double measure(F f, std::size_t num_trials, nsec_t min_time_per_trial) { - typedef capacity_wrapper cpw_t; + if(!num_trials) num_trials = 1; - Operation op; - const typename Container::size_type multiplier = op.capacity_multiplier(); - cpu_timer timer; + std::vector trials(num_trials); + for(std::size_t i = 0; i < num_trials; ++i) { + std::size_t runs = 0; + nsec_t t1; + nsec_t t2; - const std::size_t max = num_elements/multiplier; - std::size_t initial_capacity = 0; - std::size_t final_capacity = 0u; - std::size_t size = 0u; - - for(std::size_t r = 0; r != num_iterations; ++r){ - //Unroll the loop to avoid noise from loop code - int i = 0; - Container c; - if (prereserve) { - cpw_t::set_reserve(c, num_elements); - if (r == (num_iterations - 1u)) { - initial_capacity = cpw_t::get_capacity(c); - } - } - - timer.resume(); - for(std::size_t e = 0; e < max/16; ++e){ - op(c, static_cast(i++)); - op(c, static_cast(i++)); - op(c, static_cast(i++)); - op(c, static_cast(i++)); - op(c, static_cast(i++)); - op(c, static_cast(i++)); - op(c, static_cast(i++)); - op(c, static_cast(i++)); - op(c, static_cast(i++)); - op(c, static_cast(i++)); - op(c, static_cast(i++)); - op(c, static_cast(i++)); - op(c, static_cast(i++)); - op(c, static_cast(i++)); - op(c, static_cast(i++)); - op(c, static_cast(i++)); - } - - switch (max % 16) { - case 15: - op(c, static_cast(i++)); - BOOST_FALLTHROUGH; - case 14: - op(c, static_cast(i++)); - BOOST_FALLTHROUGH; - case 13: - op(c, static_cast(i++)); - BOOST_FALLTHROUGH; - case 12: - op(c, static_cast(i++)); - BOOST_FALLTHROUGH; - case 11: - op(c, static_cast(i++)); - BOOST_FALLTHROUGH; - case 10: - op(c, static_cast(i++)); - BOOST_FALLTHROUGH; - case 9: - op(c, static_cast(i++)); - BOOST_FALLTHROUGH; - case 8: - op(c, static_cast(i++)); - BOOST_FALLTHROUGH; - case 7: - op(c, static_cast(i++)); - BOOST_FALLTHROUGH; - case 6: - op(c, static_cast(i++)); - BOOST_FALLTHROUGH; - case 5: - op(c, static_cast(i++)); - BOOST_FALLTHROUGH; - case 4: - op(c, static_cast(i++)); - BOOST_FALLTHROUGH; - case 3: - op(c, static_cast(i++)); - BOOST_FALLTHROUGH; - case 2: - op(c, static_cast(i++)); - BOOST_FALLTHROUGH; - case 1: - op(c, static_cast(i++)); - BOOST_FALLTHROUGH; - default: - ; - } - - timer.stop(); - - if (r == (num_iterations - 1u)) { - final_capacity = cpw_t::get_capacity(c); - size = c.size(); - } - //c destroyed here + measure_start = t1 = boost::move_detail::nsec_clock(); + do { + clobber(); + unsigned res = f(); + escape(&res); + t2 = boost::move_detail::nsec_clock(); + ++runs; + } while((t2 - t1) < min_time_per_trial); + trials[i] = double(t2 - measure_start) / 1.0e9 / double(runs); } + std::sort(trials.begin(), trials.end()); - nanosecond_type nseconds = timer.elapsed().wall; - - std::cout << cont_name << "->" << " ns: " - << std::setw(8) - << float(nseconds)/float((num_iterations-1)*num_elements) - << '\t' - << "Size: " << size - << '\t' - << "InitCap: " << initial_capacity - << '\t' - << "FiniCap: " << final_capacity - << std::endl; + const std::size_t ts = trials.size(); + const std::size_t ts_discard = ts / 4; //drop the slowest quartile + double sum = 0.0; + for(std::size_t i = ts_discard; i < ts; ++i) sum += trials[i]; + return sum / double(ts - ts_discard); } -//Customization point implemented by every runner: run vector_test_template for -//each container that the runner wants to compare. -template -void run_containers(unsigned numit, unsigned numele, bool prereserve); - -template -void test_vectors_impl() +//Builds one Container of n elements with Operation, timing only the inserts. +//The Operation (which may own a runtime range buffer) and the reserve are set +//up in the paused region so only the insertions are measured. +template +unsigned build_once(std::size_t n, bool prereserve) { - //#define SINGLE_TEST - //#define SIMPLE_IT - #ifdef SINGLE_TEST - #ifdef NDEBUG - unsigned int numit [] = { 1000 }; - #else - unsigned int numit [] = { 20 }; - #endif - unsigned int numele [] = { 10000 }; - #elif defined SIMPLE_IT - #ifdef NDEBUG - unsigned int numit [] = { 150 }; - #else - unsigned int numit [] = { 10 }; - #endif - unsigned int numele [] = { 10000 }; - #else - #ifdef NDEBUG - unsigned int numit [] = { 1000, 10000, 100000, 1000000 }; - #else - unsigned int numit [] = { 100, 1000, 10000, 100000 }; - #endif - unsigned int numele [] = { 10000, 1000, 100, 10 }; - #endif + typedef capacity_wrapper cpw_t; + unsigned res = 0; + { + Container c; + pause_timing(); //exclude ctor + reserve + op setup + Operation op(n); + const std::size_t mult = op.capacity_multiplier(); + const std::size_t count = mult ? n / mult : 0u; + if(prereserve) cpw_t::set_reserve(c, n); + resume_timing(); + int i = 0; + for(std::size_t e = 0; e < count; ++e) + op(c, static_cast(i++)); -//#define RESERVE_ONLY 0 -#define NORESERVE_ONLY 1 + res = static_cast(c.size()); + pause_timing(); //exclude destruction + } //~Container()/~Operation() (paused) + resume_timing(); + return res; +} -//#define RESERVE_STRATEGY NORESERVE_ONLY -//#define RESERVE_STRATEGY RESERVE_ONLY +} //namespace bench_vector_detail -#ifndef RESERVE_STRATEGY - #define P_INIT 0 - #define P_END 2 -#elif RESERVE_STRATEGY == PRERESERVE_ONLY - #define P_INIT 1 - #define P_END 2 -#elif RESERVE_STRATEGY == NORESERVE_ONLY - #define P_INIT 0 - #define P_END 1 +/////////////////////////////////////////////////////////////////////////////// +// Friendly type names for the report header. +/////////////////////////////////////////////////////////////////////////////// +template inline const char* bench_type_name() { return typeid(T).name(); } +template<> inline const char* bench_type_name() { return "int"; } +template<> inline const char* bench_type_name() { return "unsigned"; } +template<> inline const char* bench_type_name() { return "long"; } +template<> inline const char* bench_type_name() { return "float"; } +template<> inline const char* bench_type_name() { return "double"; } +#ifdef BOOST_HAS_LONG_LONG +template<> inline const char* bench_type_name< ::boost::long_long_type>() { return "long long"; } #endif - for (unsigned p = P_INIT; p != P_END; ++p) { - std::cout << "---------------------------------\n"; - std::cout << "IntType:" << typeid(IntType).name() << " op:" << Operation().name() << ", prereserve: " << (p ? "1" : "0") << "\n"; - std::cout << "---------------------------------\n"; - const bool bp = p != 0; - const std::size_t it_count = sizeof(numele)/sizeof(numele[0]); - for(unsigned int i = 0; i < it_count; ++i){ - std::cout << "\n" << " ---- numit[i]: " << numit[i] << " numele[i] : " << numele[i] << " ---- \n"; - run_containers(numit[i], numele[i], bp); - } - std::cout << "---------------------------------\n---------------------------------\n"; +/////////////////////////////////////////////////////////////////////////////// +// Report: collects per (operation,size) seconds for every container column and +// prints a compact ratio table (each column divided by the first/baseline). +/////////////////////////////////////////////////////////////////////////////// +inline std::string bench_fmt2(double v) +{ + std::ostringstream o; + o << std::fixed << std::setprecision(2) << v; + return o.str(); +} + +class report +{ + public: + struct row + { + std::string op; + std::size_t size_exp; + std::size_t n_eff; + std::vector sec; //one per column; sec[0] is the baseline + }; + + report(const char* elem_name, bool prereserve) + : elem_name_(elem_name), prereserve_(prereserve) + {} + + bool has_columns() const { return !cols_.empty(); } + + void set_columns(const std::vector& names) { cols_ = names; } + + void add_row(const std::string& op, std::size_t size_exp, + std::size_t n_eff, const std::vector& sec) + { + row r; + r.op = op; + r.size_exp = size_exp; + r.n_eff = n_eff; + r.sec = sec; + rows_.push_back(r); } + + void print() const + { + const int op_w = 30, size_w = 7, col_w = 14; + + std::cout << "\n" << std::string(41, '=') << "\n" + << "element=" << elem_name_ + << " prereserve=" << (prereserve_ ? "1" : "0") << "\n"; + if(!cols_.empty()) + std::cout << "ratio vs '" << cols_[0] << "' (denominator), lower is faster\n"; + std::cout << std::string(41, '=') << "\n"; + + //Header: operation, size and one column per container. + std::cout << std::left << std::setw(op_w) << "operation" + << std::right << std::setw(size_w) << "size"; + for(std::size_t c = 0; c < cols_.size(); ++c) + std::cout << std::setw(col_w) << cols_[c]; + std::cout << "\n"; + + //Data rows: the per-container ratios. + for(std::size_t i = 0; i < rows_.size(); ++i) { + const row& r = rows_[i]; + std::ostringstream se; + se << "1.E" << r.size_exp; + std::cout << std::left << std::setw(op_w) << r.op + << std::right << std::setw(size_w) << se.str(); + const double base = r.sec.empty() ? 0.0 : r.sec[0]; + for(std::size_t c = 0; c < r.sec.size(); ++c) { + const double ratio = base > 0.0 ? r.sec[c] / base : 0.0; + std::cout << std::setw(col_w) << bench_fmt2(ratio); + } + std::cout << "\n"; + } + + //Footer: per-column geomean (vertical), then the general geomean over + //every ratio cell of the table on its own line. + std::cout << std::string(41, '-') << "\n"; + std::cout << std::left << std::setw(op_w) << "geomean (ratio)" + << std::right << std::setw(size_w) << ""; + for(std::size_t c = 0; c < cols_.size(); ++c) + std::cout << std::setw(col_w) << bench_fmt2(column_geomean(c)); + std::cout << "\n"; + std::cout << std::left << std::setw(op_w) << "general geomean" + << std::right << bench_fmt2(general_geomean()) << "\n"; + } + + private: + //Geomean of one container column's ratios across all rows (vertical). + double column_geomean(std::size_t c) const + { + double log_sum = 0.0; + std::size_t count = 0; + for(std::size_t i = 0; i < rows_.size(); ++i) { + const row& r = rows_[i]; + if(c < r.sec.size() && !r.sec.empty() && r.sec[0] > 0.0 && r.sec[c] > 0.0) { + log_sum += std::log(r.sec[c] / r.sec[0]); + ++count; + } + } + return count ? std::exp(log_sum / double(count)) : 0.0; + } + + //Geomean over every ratio cell of the table (all rows and columns). + double general_geomean() const + { + double log_sum = 0.0; + std::size_t count = 0; + for(std::size_t i = 0; i < rows_.size(); ++i) { + const row& r = rows_[i]; + const double base = r.sec.empty() ? 0.0 : r.sec[0]; + if(base > 0.0) { + for(std::size_t c = 0; c < r.sec.size(); ++c) { + if(r.sec[c] > 0.0) { log_sum += std::log(r.sec[c] / base); ++count; } + } + } + } + return count ? std::exp(log_sum / double(count)) : 0.0; + } + + std::string elem_name_; + bool prereserve_; + std::vector cols_; + std::vector rows_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// runner: the customization point's argument. A runner registers each container +// with add(name); the FIRST one registered is the baseline. add() +// measures the container across the whole size sweep; flush() (called by the +// harness) appends the rows to the shared report. +/////////////////////////////////////////////////////////////////////////////// +inline std::size_t bench_num_sizes() +{ return bench_vector_defaults::max_size_exp - bench_vector_defaults::min_size_exp + 1u; } + +template +class runner +{ + public: + runner(report& rep, bool prereserve) + : rep_(rep), prereserve_(prereserve), sec_by_size_(bench_num_sizes()) + {} + + template + void add(const char* name) + { + using namespace bench_vector_detail; + names_.push_back(name); + for(std::size_t si = 0; si < bench_num_sizes(); ++si) { + const std::size_t exp = bench_vector_defaults::min_size_exp + si; + const std::size_t n = bench_pow10(exp); + const double sec = measure( + container_build(n, prereserve_), + bench_vector_defaults::num_trials, + bench_vector_defaults::min_time_per_trial); + sec_by_size_[si].push_back(sec); + } + } + + void flush() + { + if(!rep_.has_columns()) + rep_.set_columns(names_); + for(std::size_t si = 0; si < bench_num_sizes(); ++si) { + const std::size_t exp = bench_vector_defaults::min_size_exp + si; + const std::size_t n = bench_pow10(exp); + Operation op(n); + const std::size_t mult = op.capacity_multiplier(); + const std::size_t n_eff = mult ? (n / mult) * mult : 0u; + rep_.add_row(op.name(), exp, n_eff, sec_by_size_[si]); + } + } + + private: + //Callable adapter so measure() (which expects f()) can build a Container. + template + struct container_build + { + std::size_t n; + bool prereserve; + container_build(std::size_t n_, bool pr) : n(n_), prereserve(pr) {} + unsigned operator()() const + { return bench_vector_detail::build_once(n, prereserve); } + }; + + report& rep_; + bool prereserve_; + std::vector names_; + std::vector > sec_by_size_; //[size_index][column] +}; + +//Customization point implemented by every runner: register (with add<>()) each +//container it wants to compare. The first one registered is the baseline. +template +void run_containers(runner& r); + +/////////////////////////////////////////////////////////////////////////////// +// Drivers. +/////////////////////////////////////////////////////////////////////////////// +template +void add_operation(report& rep, bool prereserve) +{ + runner r(rep, prereserve); + run_containers(r); + r.flush(); +} + +template +void test_vectors_pass(bool prereserve) +{ + report rep(bench_type_name(), prereserve); + + //end + add_operation >(rep, prereserve); + #if BOOST_CXX_VERSION >= 201103L + add_operation >(rep, prereserve); + #endif + add_operation >(rep, prereserve); + add_operation >(rep, prereserve); + + //near end + add_operation >(rep, prereserve); + #if BOOST_CXX_VERSION >= 201103L + add_operation >(rep, prereserve); + #endif + add_operation >(rep, prereserve); + add_operation >(rep, prereserve); + + rep.print(); } template void test_vectors() { - //end - test_vectors_impl >(); - #if BOOST_CXX_VERSION >= 201103L - test_vectors_impl >(); - #endif + std::cout << "Benchmark config: sizes 1.E" << bench_vector_defaults::min_size_exp + << " .. 1.E" << bench_vector_defaults::max_size_exp + << ", trials " << bench_vector_defaults::num_trials + << ", min " << (bench_vector_defaults::min_time_per_trial / 1000000) + << " ms/trial\n"; - test_vectors_impl >(); - test_vectors_impl >(); - - //near end - test_vectors_impl >(); - #if BOOST_CXX_VERSION >= 201103L - test_vectors_impl >(); - #endif - test_vectors_impl >(); - test_vectors_impl >(); + if(bench_vector_defaults::run_noreserve) + test_vectors_pass(false); + if(bench_vector_defaults::run_prereserve) + test_vectors_pass(true); } #endif //BOOST_CONTAINER_BENCH_VECTOR_COMMON_HPP