// Copyright 2018-2024 Emil Dotchevski and Reverge Studios, Inc. // 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 benchmark.md #include #ifndef BOOST_LEAF_NO_EXCEPTIONS # error Please disable exception handling. #endif #if BOOST_LEAF_CFG_DIAGNOSTICS # error Please disable diagnostics. #endif #ifdef _MSC_VER # define NOINLINE __declspec(noinline) # define ALWAYS_INLINE __forceinline #else # define NOINLINE __attribute__((noinline)) # define ALWAYS_INLINE __attribute__((always_inline)) inline #endif #include #include #include #include #include #include #include #include #include #include #include namespace boost { [[noreturn]] void throw_exception( std::exception const & e ) { std::cerr << "Terminating due to a C++ exception under BOOST_LEAF_NO_EXCEPTIONS: " << e.what(); std::terminate(); } struct source_location; [[noreturn]] void throw_exception( std::exception const & e, boost::source_location const & ) { throw_exception(e); } } ////////////////////////////////////// namespace leaf = boost::leaf; #define USING_RESULT_TYPE "leaf::result" ////////////////////////////////////// enum class e_error_code { ec0, ec1, ec2, ec3 }; struct e_system_error { int value; std::string what; }; struct e_heavy_payload { std::array value; }; template leaf::error_id make_error() noexcept; template <> inline leaf::error_id make_error() noexcept { switch(std::rand()%4) { default: return leaf::new_error(e_error_code::ec0); case 1: return leaf::new_error(e_error_code::ec1); case 2: return leaf::new_error(e_error_code::ec2); case 3: return leaf::new_error(e_error_code::ec3); } } template <> inline leaf::error_id make_error() noexcept { return std::error_code(std::rand(), std::system_category()); } template <> inline leaf::error_id make_error() noexcept { return leaf::new_error( e_system_error { std::rand(), std::string(std::rand()%32, ' ') } ); } template <> inline leaf::error_id make_error() noexcept { e_heavy_payload e; std::fill(e.value.begin(), e.value.end(), std::rand()); return leaf::new_error(e); } inline bool should_fail( int failure_rate ) noexcept { assert(failure_rate>=0); assert(failure_rate<=100); return (std::rand()%100) < failure_rate; } inline int handle_error( e_error_code e ) noexcept { return int(e); } inline int handle_error( std::error_code const & e ) noexcept { return e.value(); } inline int handle_error( e_system_error const & e ) noexcept { return e.value + e.what.size(); } inline int handle_error( e_heavy_payload const & e ) noexcept { return std::accumulate(e.value.begin(), e.value.end(), 0); } ////////////////////////////////////// // This is used to change the "success" type at each level. // Generally, functions return values of different types. template struct select_result_type; template struct select_result_type { using type = leaf::result; // Does not depend on E }; template struct select_result_type { using type = leaf::result; // Does not depend on E }; template using select_result_t = typename select_result_type::type; ////////////////////////////////////// template struct benchmark { using e_type = E; NOINLINE static select_result_t f( int failure_rate ) noexcept { BOOST_LEAF_AUTO(x, (benchmark::f(failure_rate))); return x+1; } }; template struct benchmark<1, E> { using e_type = E; NOINLINE static select_result_t<1, E> f( int failure_rate ) noexcept { if( should_fail(failure_rate) ) return make_error(); else return std::rand(); } }; ////////////////////////////////////// template NOINLINE int runner( int failure_rate ) noexcept { return leaf::try_handle_all( [=] { return Benchmark::f(failure_rate); }, []( typename Benchmark::e_type const & e ) { return handle_error(e); }, [] { return -1; } ); } ////////////////////////////////////// std::fstream append_csv() { if( FILE * f = fopen("benchmark.csv","rb") ) { fclose(f); return std::fstream("benchmark.csv", std::fstream::out | std::fstream::app); } else { std::fstream fs("benchmark.csv", std::fstream::out | std::fstream::app); fs << "\"Result Type\",2%,98%\n"; return fs; } } template int print_elapsed_time( int iteration_count, F && f ) { auto start = std::chrono::steady_clock::now(); int val = 0; for( int i = 0; i!=iteration_count; ++i ) val += std::forward(f)(); auto stop = std::chrono::steady_clock::now(); int elapsed = std::chrono::duration_cast(stop-start).count(); std::cout << std::right << std::setw(9) << elapsed; append_csv() << ',' << elapsed; return val; } ////////////////////////////////////// template int benchmark_type( char const * type_name, int iteration_count ) { int x=0; append_csv() << "\"" USING_RESULT_TYPE "\""; std::cout << '\n' << std::left << std::setw(16) << type_name << '|'; std::srand(0); x += print_elapsed_time( iteration_count, [] { return runner>(2); } ); std::cout << " |"; std::srand(0); x += print_elapsed_time( iteration_count, [] { return runner>(98); } ); append_csv() << '\n'; return x; } ////////////////////////////////////// int main() { int const depth = 10; int const iteration_count = 10000000; std::cout << iteration_count << " iterations, call depth " << depth << ", sizeof(e_heavy_payload) = " << sizeof(e_heavy_payload) << "\n" USING_RESULT_TYPE "\n" "Error type | 2% (μs) | 98% (μs)\n" "----------------|----------|---------"; int r = 0; r += benchmark_type("e_error_code", iteration_count); r += benchmark_type("std::error_code", iteration_count); r += benchmark_type("e_system_error", iteration_count); r += benchmark_type("e_heavy_payload", iteration_count); std::cout << '\n'; // std::cout << std::rand() << '\n'; return r; }