// Copyright (c) 2018-2020 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 #ifndef BENCHMARK_WHAT # define BENCHMARK_WHAT 0 #endif #if BENCHMARK_WHAT == 0 # ifndef TL_EXPECTED_HPP # include "tl/expected.hpp" # endif # define BENCHMARK_SUCCESS(e) e # define BENCHMARK_FAILURE(e) tl::make_unexpected(e) # define BENCHMARK_TRY(v,r)\ auto && _r_##v = r;\ if( !_r_##v )\ return BENCHMARK_FAILURE(_r_##v.error());\ auto && v = _r_##v.value() #else # include # include # define BENCHMARK_SUCCESS(e) boost::outcome_v2::success(e) # define BENCHMARK_FAILURE(e) boost::outcome_v2::failure(e) # define BENCHMARK_TRY BOOST_OUTCOME_TRY # ifndef BOOST_NO_EXCEPTIONS # error Please disable exception handling. # endif #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 { void throw_exception( std::exception const & e ) { std::cerr << "Terminating due to a C++ exception under BOOST_NO_EXCEPTIONS: " << e.what(); std::terminate(); } struct source_location; void throw_exception( std::exception const & e, boost::source_location const & ) { throw_exception(e); } } ////////////////////////////////////// #if BENCHMARK_WHAT == 0 // tl::expected # define USING_RESULT_TYPE "tl::expected" template using result = tl::expected; #elif BENCHMARK_WHAT == 1 // outcome::result # define USING_RESULT_TYPE "outcome::result" template using result = boost::outcome_v2::std_result; #elif BENCHMARK_WHAT == 2 // outcome::outcome # define USING_RESULT_TYPE "outcome::outcome" template using result = boost::outcome_v2::std_outcome; #else # error Benchmark what? #endif ////////////////////////////////////// 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 E make_error() noexcept; template <> inline e_error_code make_error() noexcept { switch(std::rand()%4) { default: return e_error_code::ec0; case 1: return e_error_code::ec1; case 2: return e_error_code::ec2; case 3: return e_error_code::ec3; } } template <> inline std::error_code make_error() noexcept { return std::error_code(std::rand(), std::system_category()); } template <> inline e_system_error make_error() noexcept { return { std::rand(), std::string(std::rand()%32, ' ') }; } template <> inline e_heavy_payload make_error() noexcept { e_heavy_payload e; std::fill(e.value.begin(), e.value.end(), std::rand()); return 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 = result; }; template struct select_result_type { using type = result; }; 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 { BENCHMARK_TRY(x, (benchmark::f(failure_rate))); return BENCHMARK_SUCCESS(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 BENCHMARK_FAILURE(make_error()); else return BENCHMARK_SUCCESS(std::rand()); } }; ////////////////////////////////////// template NOINLINE int runner( int failure_rate ) noexcept { if( auto r = Benchmark::f(failure_rate) ) return r.value(); else return handle_error(r.error()); } ////////////////////////////////////// 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; }