diff --git a/bench/Jamfile b/bench/Jamfile index d05a6ba0..687163b4 100644 --- a/bench/Jamfile +++ b/bench/Jamfile @@ -40,13 +40,21 @@ exe bench : $(has_rapidjson)BOOST_JSON_HAS_RAPIDJSON ; +exe bench-conv + : bench-conv.cpp + /boost/json//boost_json + : ../test + ../example + ; + install bench-local : bench : . true ; explicit bench-local ; always bench-local ; local bench-files = [ glob-tree-ex data : *.json ] ; notfile run : @run-bench : bench : : $(bench-files) ; -explicit run ; +notfile run-conv : @run-bench : bench-conv : : $(bench-files) ; +explicit run run-conv ; rule run-bench ( target : sources * : props * ) { diff --git a/bench/bench-conv.cpp b/bench/bench-conv.cpp new file mode 100644 index 00000000..dd586018 --- /dev/null +++ b/bench/bench-conv.cpp @@ -0,0 +1,931 @@ +// +// Copyright (c) 2025 Dmitry Arkhipov (grisumbras@yandex.ru) +// +// 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) +// +// Official repository: https://github.com/boostorg/json +// + + +#include + +#if defined(BOOST_JSON_USE_SSE2) +# define RAPIDJSON_SSE2 +# define SSE2_ARCH_SUFFIX "/sse2" +#else +# define SSE2_ARCH_SUFFIX "" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "test_suite.hpp" +#include "apache_builds.hpp" +#include "canada.hpp" +#include "citm_catalog.hpp" + +using namespace boost::json; + +namespace { + +namespace mpl = boost::mp11; + +::test_suite::debug_stream dout(std::cerr); +std::stringstream strout; + +#if defined(__clang__) +string_view toolset = "clang"; +#elif defined(__GNUC__) +string_view toolset = "gcc"; +#elif defined(_MSC_VER) +string_view toolset = "msvc"; +#else +string_view toolset = "unknown"; +#endif + +#if BOOST_JSON_ARCH == 32 +string_view arch = "x86" SSE2_ARCH_SUFFIX; +#elif BOOST_JSON_ARCH == 64 +string_view arch = "x64" SSE2_ARCH_SUFFIX; +#else +#error Unknown architecture. +#endif + +std::string s_tests = "ps"; +std::string s_impls = "bsod"; +std::size_t s_trials = 6; +std::string s_branch = ""; +std::string s_alloc = "p"; +std::string s_num_mode = "i"; + +bool +parse_option(char const* s) +{ + if(*s == 0) + return false; + + char opt = *s++; + + if(*s++ != ':') + return false; + + switch(opt) + { + case 't': + s_tests = s; + break; + + case 'i': + s_impls = s; + break; + + case 'n': + { + int k = std::atoi(s); + + if(k > 0) + s_trials = k; + else + return false; + } + break; + + case 'b': + s_branch = s; + break; + + case 'a': + s_alloc = s; + break; + + case 'm': + s_num_mode = s; + break; + } + + return true; +} + +struct file_item +{ + string_view name; + std::string text; + int index; +}; + +using file_list = std::vector; + +std::string +load_file(char const* path) +{ + FILE* f = fopen(path, "rb"); + fseek(f, 0, SEEK_END); + auto const size = ftell(f); + std::string s; + s.resize(size); + fseek(f, 0, SEEK_SET); + auto const nread = fread(&s[0], 1, size, f); + s.resize(nread); + fclose(f); + return s; +} + +#if defined(BOOST_DESCRIBE_CXX14) +using supported_files = mpl::mp_list< + apache_builds_support, + canada_support, + citm_catalog_support >; +#else +struct mock_type_support +{ + using type = int; + static constexpr char const* const name = ""; +}; +using supported_files = mpl::mp_list; +#endif // defined(BOOST_DESCRIBE_CXX14) + +using supported_file_count = mpl::mp_size; + +template< class I > +using supported_file_at = mpl::mp_at; + +struct file_matcher +{ + string_view file_name; + int& result; + + template< class I > + void + operator()(I) + { + using supported_file = supported_file_at; + std::size_t const name_len = std::strlen(supported_file::name); + + std::size_t pos = file_name.rfind( + supported_file::name, string_view::npos, name_len); + if(pos == string_view::npos) + return; + if( pos + name_len != file_name.size() ) + return; + if( pos != 0 && file_name[pos - 1] != '/' + && file_name[pos - 1] != '\\' ) + return; + + result = static_cast(I::value); + } +}; + +int +find_supported_file(string_view file_name) +{ + int result = -1; + mpl::mp_for_each< mpl::mp_iota >( + file_matcher{file_name, result} ); + return result; +} + +using clock_type = std::chrono::steady_clock; + +class any_impl +{ + std::string name_; + parse_options popts_; + + virtual + clock_type::duration + parse(file_item const& fi, std::size_t repeat) const = 0; + + virtual + clock_type::duration + serialize(file_item const& fi, std::size_t repeat) const = 0; + +protected: + clock_type::duration + skip() const + { + return clock_type::duration::zero(); + } + + parse_options const& + get_parse_options() const noexcept + { + return popts_; + } + +public: + using operation = auto (any_impl::*) + (file_item const& fi, std::size_t repeat) const + -> clock_type::duration; + + any_impl( + string_view base_name, + string_view flavor, + bool is_boost, + bool is_pool, + parse_options const& popts) + : popts_(popts) + { + std::string extra; + switch( popts_.numbers ) + { + case number_precision::precise: + extra = "precise numbers"; + break; + case number_precision::none: + extra = "no numbers"; + break; + default: + break; + } + + if( is_pool ) + { + if( !extra.empty() ) + extra = '+' + extra; + extra = "pool" + extra; + } + + if( !flavor.empty() ) + { + if( !extra.empty() ) + extra = '+' + extra; + extra.insert( extra.begin(), flavor.begin(), flavor.end() ); + } + + if( !extra.empty() ) + extra = " (" + extra + ')'; + + if( is_boost && !s_branch.empty() ) + extra += ' ' + s_branch; + + name_ = base_name; + name_ += extra; + } + + virtual ~any_impl() = default; + + clock_type::duration + bench(string_view verb, file_item const& fi, std::size_t repeat) const + { + if( fi.index < 0 ) + return skip(); + + if(verb == "Parse") + return parse(fi, repeat); + + BOOST_ASSERT(verb == "Serialize"); + return serialize(fi, repeat); + } + + string_view + name() const noexcept + { + return name_; + } +}; + +using impl_ptr = std::unique_ptr; +using impl_list = std::vector; + +struct sample +{ + std::size_t calls; + std::size_t millis; + std::size_t mbs; +}; + +// Returns the number of invocations per second +template< + class Rep, + class Period, + class F> +sample +run_for(std::chrono::duration interval, F&& f) +{ + clock_type::duration elapsed(0); + std::size_t n = 0; + do + { + elapsed += f(); + ++n; + } + while(elapsed < interval); + return { + n, + static_cast( + std::chrono::duration_cast( + elapsed).count()), + 0}; +} + +std::size_t +megabytes_per_second( + file_item const& file, std::size_t calls, std::size_t millis) +{ + double result = file.text.size(); + result /= 1024 * 1024; // size in megabytes + result *= calls; + result /= millis; // mb per ms + result *= 1000; // mb per s + return static_cast(0.5 + result); // round up +} + +std::ostream& +print_prefix( + std::ostream& os, file_item const& file, any_impl const& impl, + string_view verb) +{ + return os << verb << " " << file.name << "," << toolset << " " << + arch << "," << impl.name(); +} + +bool +do_test(file_list const & vf, impl_list const & vi, char test) +{ + string_view verb; + switch(test) + { + case 'p': + verb = "Parse"; + break; + case 's': + verb = "Serialize"; + break; + default: + std::cerr << "Unknown test type: '" << test << "'\n"; + return false; + } + + std::vector trial; + for(unsigned i = 0; i < vf.size(); ++i) + { + for(unsigned j = 0; j < vi.size(); ++j) + { + trial.clear(); + std::size_t repeat = 1; + + auto const f = [&]{ + return vi[j].get()->bench(verb, vf[i], repeat); + }; + + // helps with the caching, which reduces noise; also, we determine + // if this configuration should be skipped altogether + auto const elapsed = f(); + if( elapsed == std::chrono::milliseconds::zero() ) + { + print_prefix(dout, vf[i], *vi[j], verb) + << "," << "N/A" + << "," << "N/A" + << "," << "N/A" + << "\n"; + print_prefix(strout, vf[i], *vi[j], verb) + << "," << "N/A" << "\n"; + continue; + } + + repeat = 1000; + for(unsigned k = 0; k < s_trials; ++k) + { + auto result = run_for(std::chrono::seconds(5), f); + result.calls *= repeat; + result.mbs = megabytes_per_second( + vf[i], result.calls, result.millis); + print_prefix(dout, vf[i], *vi[j], verb) + << "," << result.calls + << "," << result.millis + << "," << result.mbs + << "\n"; + trial.push_back(result); + // adjust repeat to avoid overlong tests + repeat = 250 * result.calls / result.millis; + } + + // clean up the samples + std::sort( + trial.begin(), + trial.end(), + [](sample const& lhs, sample const& rhs) + { + return lhs.mbs < rhs.mbs; + }); + if(s_trials >= 6) + { + // discard worst 2 + trial.erase(trial.begin(), trial.begin() + 2); + // discard best 1 + trial.resize(trial.size() - 1); + } + else if(s_trials > 3) + { + trial.erase(trial.begin(), trial.begin() + s_trials - 3); + } + // average + auto const calls = std::accumulate( + trial.begin(), trial.end(), + std::size_t{}, + [](std::size_t lhs, sample const& rhs) + { + return lhs + rhs.calls; + }); + auto const millis = std::accumulate( + trial.begin(), trial.end(), + std::size_t{}, + [](std::size_t lhs, sample const& rhs) + { + return lhs + rhs.millis; + }); + auto const mbs = megabytes_per_second(vf[i], calls, millis); + print_prefix(strout, vf[i], *vi[j], verb) << "," << mbs << "\n"; + } + } + return true; +} + +class base_boost_impl : public any_impl +{ +protected: + bool is_pool_; + + struct delete_data + { + std::size_t index; + + struct deleter + { + void* data; + + template< class I > + void operator()(I) const + { + using data_type = typename supported_file_at::type; + delete reinterpret_cast(data); + } + }; + + void operator()(void* data) + { + mpl::mp_with_index( index, deleter{data} ); + } + }; + + using data_holder = std::unique_ptr; + + struct convert_from_value + { + value const& jv; + + template< class I > + void operator()(I) const + { + using data_type = typename supported_file_at::type; + auto data = value_to(jv); + (void)data; + } + }; + + struct convert_to_value + { + data_holder& data; + value& jv; + + template< class I > + BOOST_NOINLINE + void operator()(I) const + { + using data_type = typename supported_file_at::type; + value_from( *reinterpret_cast( data.get() ), jv ); + } + }; + + struct data_constructor + { + data_holder& data; + value const& jv; + + template< class I > + void operator()(I) const + { + using data_type = typename supported_file_at::type; + auto const ptr = new data_type{}; + data.reset(ptr); + *ptr = value_to(jv); + } + }; + + static + data_holder + construct_data(std::size_t file_index, value const& jv) + { + data_holder result; + mpl::mp_with_index( + file_index, data_constructor{result, jv} ); + return result; + } + +public: + base_boost_impl( + string_view flavor, bool is_pool, parse_options const& popts) + : any_impl("boost", flavor, true, is_pool, popts) + , is_pool_(is_pool) + {} +}; + +class boost_impl : public base_boost_impl +{ +public: + boost_impl(bool is_pool, parse_options const& popts) + : base_boost_impl("", is_pool, popts) + {} + + clock_type::duration + parse(file_item const& fi, std::size_t repeat) const override + { + auto const start = clock_type::now(); + parser p( {}, get_parse_options() ); + while(repeat--) + { + monotonic_resource mr; + storage_ptr sp; + if( is_pool_ ) + sp = &mr; + p.reset( std::move(sp) ); + + p.write( fi.text.data(), fi.text.size() ); + mpl::mp_with_index( + fi.index, convert_from_value{p.release()}); + } + return clock_type::now() - start; + } + + clock_type::duration + serialize(file_item const& fi, std::size_t repeat) const override + { + monotonic_resource mr; + storage_ptr sp; + if(is_pool_) + sp = &mr; + value jv = boost::json::parse( fi.text, std::move(sp) ); + data_holder data = construct_data(fi.index, jv); + + auto const start = clock_type::now(); + serializer sr; + string out; + out.reserve(512); + while(repeat--) + { + mpl::mp_with_index( + fi.index, convert_to_value{data, jv}); + sr.reset(&jv); + out.clear(); + for(;;) + { + out.grow(sr.read( + out.end(), + out.capacity() - + out.size()).size()); + if(sr.done()) + break; + out.reserve( + out.capacity() + 1); + } + } + return clock_type::now() - start; + } +}; + +class boost_simple_impl : public base_boost_impl +{ +public: + boost_simple_impl(bool is_pool, parse_options const& popts) + : base_boost_impl("convenient", is_pool, popts) + {} + + clock_type::duration + parse(file_item const& fi, std::size_t repeat) const override + { + auto const start = clock_type::now(); + while(repeat--) + { + monotonic_resource mr; + storage_ptr sp; + if( is_pool_ ) + sp = &mr; + + auto jv = boost::json::parse( + fi.text, std::move(sp), get_parse_options() ); + mpl::mp_with_index( + fi.index, convert_from_value{jv}); + } + return clock_type::now() - start; + } + + clock_type::duration + serialize(file_item const& fi, std::size_t repeat) const override + { + monotonic_resource mr; + storage_ptr sp; + if(is_pool_) + sp = &mr; + auto jv = boost::json::parse( fi.text, std::move(sp) ); + auto data = construct_data(fi.index, jv); + + auto const start = clock_type::now(); + std::string out; + while(repeat--) + { + mpl::mp_with_index( + fi.index, convert_to_value{data, jv}); + out = boost::json::serialize(jv); + } + return clock_type::now() - start; + } +}; + +class boost_operator_impl : public base_boost_impl +{ +public: + boost_operator_impl(bool is_pool, parse_options const& popts) + : base_boost_impl("operators", is_pool, popts) + {} + + clock_type::duration + parse(file_item const& fi, std::size_t repeat) const override + { + std::istringstream is(fi.text); + is.exceptions(std::ios::failbit); + + auto const start = clock_type::now(); + while(repeat--) + { + monotonic_resource mr; + storage_ptr sp; + if( is_pool_ ) + sp = &mr; + + value jv( std::move(sp) ); + is.seekg(0); + is >> get_parse_options() >> jv; + mpl::mp_with_index( + fi.index, convert_from_value{jv}); + } + return clock_type::now() - start; + } + + clock_type::duration + serialize(file_item const& fi, std::size_t repeat) const override + { + monotonic_resource mr; + storage_ptr sp; + if(is_pool_) + sp = &mr; + + auto jv = boost::json::parse( fi.text, std::move(sp) ); + auto data = construct_data(fi.index, jv); + + auto const start = clock_type::now(); + std::string out; + while(repeat--) + { + mpl::mp_with_index( + fi.index, convert_to_value{data, jv}); + std::ostringstream os; + os.exceptions(std::ios::failbit); + os << jv; + out = os.str(); + } + return clock_type::now() - start; + } +}; + +class boost_direct_impl : public base_boost_impl +{ + struct parser + { + file_item const& fi; + parse_options const& opts; + std::size_t& repeat; + + template< class I > + clock_type::duration operator()(I) const + { + auto const start = clock_type::now(); + while(repeat--) + { + using data_type = typename supported_file_at::type; + data_type v; + boost::system::error_code ec; + parser_for p(opts, &v); + + auto const n = p.write_some( + false, fi.text.data(), fi.text.size(), ec ); + if( !ec.failed() && n < fi.text.size() ) + ec = error::extra_data; + if( ec.failed() ) + throw boost::system::system_error(ec); + } + return clock_type::now() - start; + } + }; + + struct serializer + { + file_item const& fi; + parse_options const& opts; + std::size_t& repeat; + + template< class I > + clock_type::duration operator()(I) const + { + typename supported_file_at::type v; + boost::json::parse_into(v, fi.text); + + auto const start = clock_type::now(); + boost::json::serializer sr; + string out; + out.reserve(512); + while(repeat--) + { + sr.reset(&v); + out.clear(); + for(;;) + { + auto const sv = sr.read( + out.end(), out.capacity() - out.size() ); + out.grow( sv.size() ); + if( sr.done() ) + break; + out.reserve( out.capacity() + 1 ); + } + } + return clock_type::now() - start; + } + }; + +public: + boost_direct_impl(parse_options const& popts) + : base_boost_impl("direct", false, popts) + {} + + clock_type::duration + parse(file_item const& fi, std::size_t repeat) const override + { + return mpl::mp_with_index( + fi.index, parser{fi, get_parse_options(), repeat} ); + } + + clock_type::duration + serialize(file_item const& fi, std::size_t repeat) const override + { + return mpl::mp_with_index( + fi.index, serializer{fi, get_parse_options(), repeat} ); + } +}; + +bool +add_impl(impl_list & vi, char kind, char alloc, char num) +{ + parse_options popts; + switch(num) + { + case 'i': + popts.numbers = number_precision::imprecise; + break; + case 'p': + popts.numbers = number_precision::precise; + break; + case 'n': + popts.numbers = number_precision::none; + break; + default: + return false; + } + bool const is_pool = alloc == 'p'; + + impl_ptr impl; + switch(kind) + { + case 'b': + impl.reset( new boost_impl(is_pool, popts) ); + break; + + case 's': + impl.reset( new boost_simple_impl(is_pool, popts) ); + break; + + case 'o': + impl.reset( new boost_operator_impl(is_pool, popts) ); + break; + + case 'd': + impl.reset( new boost_direct_impl(popts) ); + break; + + default: + std::cerr << "Unknown implementation: '" << kind << "'\n"; + return false; + } + + vi.emplace_back( std::move(impl) ); + return true; +} + +} // namespace + +int +main(int const argc, char const* const* const argv) +{ + if(argc < 2) + { + std::cerr << + "Usage: bench-direct [options...] ...\n" + "\n" + "Options: -t:[p][s] Test parsing, serialization or both\n" + " (default both)\n" + " -i:[b][u][s][o][d][r][n] Test the specified implementations\n" + " (b: Boost.JSON)\n" + " (s: Boost.JSON, convenient functions)\n" + " (o: Boost.JSON, stream operators)\n" + " (d: Boost.JSON, direct conversion)\n" + " (default all)\n" + " -a:[p][d] Memory allocation strategy\n" + " (p: memory pool)\n" + " (d: default strategy)\n" + " (default memory pool)\n" + " -n: Number of trials (default 6)\n" + " -b: Branch label for boost implementations\n" + " -m:[i][p][n] Number parsing mode\n" + " (i: imprecise)\n" + " (p: precise)\n" + " (n: none)\n" + " (default imprecise)\n" + ; + + return 4; + } + + file_list vf; + + for(int i = 1; i < argc; ++i) + { + char const* s = argv[i]; + + if(*s == '-') + { + if( !parse_option(s + 1) ) + std::cerr + << "Unrecognized or incorrect option: '" << s << "'\n"; + } + else + { + string_view file_name = argv[i]; + vf.emplace_back( + file_item{ + file_name, load_file(s), find_supported_file(file_name)}); + } + } + + try + { + impl_list vi; + for( char impl: s_impls ) + for( char alloc: s_alloc ) + for( char num: s_num_mode ) + add_impl(vi, impl, alloc, num); + + // remove duplicate implementations + std::sort( + vi.begin(), + vi.end(), + [](impl_ptr const& l, impl_ptr const& r) + { + return l->name() < r->name(); + }); + auto const it = std::unique( + vi.begin(), + vi.end(), + [](impl_ptr const& l, impl_ptr const& r) + { + return l->name() == r->name(); + }); + vi.erase( it, vi.end() ); + + for(char ch: s_tests) + do_test(vf, vi, ch); + + dout << "\n" << strout.str(); + } + catch(boost::system::system_error const& se) + { + dout << se.what() << std::endl; + } + + return 0; +} diff --git a/bench/bench.cpp b/bench/bench.cpp index 55d0d1e9..432e395d 100644 --- a/bench/bench.cpp +++ b/bench/bench.cpp @@ -51,13 +51,12 @@ */ std::string s_tests = "ps"; -std::string s_impls = "busodrn"; +std::string s_impls = "busorn"; std::size_t s_trials = 6; std::string s_branch = ""; std::string s_alloc = "p"; std::string s_num_mode = "i"; std::string s_file_io = "n"; -std::string s_conversion = "n"; namespace boost { namespace json { @@ -95,143 +94,11 @@ struct file_item using file_list = std::vector; -#if defined(BOOST_DESCRIBE_CXX14) -using supported_files = mp11::mp_list< - apache_builds_support, - canada_support, - citm_catalog_support >; -#else -struct mock_type_support -{ - using type = int; - static constexpr char const* const name = ""; -}; -using supported_files = mp11::mp_list; -#endif // defined(BOOST_DESCRIBE_CXX14) - -using supported_file_count = mp11::mp_size; - -template< class I > -using supported_file_at = mp11::mp_at; - -struct file_matcher -{ - file_item const& fi; - int& result; - - template< class I > - void operator()(I) - { - using supported_file = supported_file_at; - std::size_t const name_len = std::strlen(supported_file::name); - - std::size_t pos = fi.name.rfind( - supported_file::name, string_view::npos, name_len); - if(pos == string_view::npos) - return; - if( pos + name_len != fi.name.size() ) - return; - if( pos != 0 && fi.name[pos - 1] != '/' - && fi.name[pos - 1] != '\\' ) - return; - - result = static_cast(I::value); - } -}; - -int -find_supported_file(file_item const& fi) -{ - int result = -1; - mp11::mp_for_each< mp11::mp_iota >( - file_matcher{fi, result} ); - return result; -} - -struct convert_from_value -{ - value const& jv; - - template< class I > - void operator()(I) const - { - using data_type = typename supported_file_at::type; - auto data = value_to(jv); - (void)data; - } -}; - -struct delete_data -{ - struct deleter - { - void* data; - - template< class I > - void operator()(I) const - { - using data_type = typename supported_file_at::type; - delete reinterpret_cast(data); - } - }; - - int index; - - void operator()(void* data) - { - if( index >= 0 ) - mp11::mp_with_index( index, deleter{data} ); - } -}; -using data_holder = std::unique_ptr; - -struct data_constructor -{ - data_holder& data; - value const& jv; - - template< class I > - void operator()(I) const - { - using data_type = typename supported_file_at::type; - auto const ptr = new data_type{}; - data.reset(ptr); - *ptr = value_to(jv); - } -}; - -data_holder -construct_data(int file_index, value const& jv) -{ - data_holder result( nullptr, {file_index} ); - if( file_index >= 0 ) - mp11::mp_with_index( - file_index, data_constructor{result, jv} ); - return result; -} - -struct convert_to_value -{ - data_holder& data; - value& jv; - - template< class I > - BOOST_NOINLINE - void operator()(I) const - { - using data_type = typename supported_file_at::type; - value_from( - *reinterpret_cast( data.get() ), jv); - } -}; - class any_impl { std::string name_; parse_options popts_; bool with_file_io_; - bool with_conversion_; - mutable int file_index_ = -1; virtual clock_type::duration @@ -258,12 +125,6 @@ protected: return popts_; } - int - file_index() const noexcept - { - return file_index_; - } - public: using operation = auto (any_impl::*) (file_item const& fi, std::size_t repeat) const @@ -275,11 +136,9 @@ public: bool is_boost, bool is_pool, bool with_file_io, - bool with_conversion, parse_options const& popts) : popts_(popts) , with_file_io_(with_file_io) - , with_conversion_(with_conversion) { std::string extra; switch( popts_.numbers ) @@ -294,13 +153,6 @@ public: break; } - if( with_conversion ) - { - if( !extra.empty() ) - extra = '+' + extra; - extra = "conversion" + extra; - } - if( with_file_io_ ) { if( !extra.empty() ) @@ -337,15 +189,6 @@ public: clock_type::duration bench(string_view verb, file_item const& fi, std::size_t repeat) const { - if( with_conversion_ ) - { - if( ( file_index_ = find_supported_file(fi) ) < 0 ) - return skip(); - } - else - { - file_index_ = -1; - } if(verb == "Parse") { @@ -537,39 +380,13 @@ class base_boost_impl : public any_impl protected: bool is_pool_; - BOOST_FORCEINLINE - void - maybe_convert_from_value(value const& jv) const - { - if( file_index() >= 0 ) - mp11::mp_with_index( - file_index(), convert_from_value{jv}); - } - - BOOST_FORCEINLINE - void - maybe_convert_to_value(data_holder& data, value& jv) const - { - if( file_index() >= 0 ) - mp11::mp_with_index( - file_index(), convert_to_value{data, jv}); - } - public: base_boost_impl( string_view flavor, bool is_pool, bool with_file_io, - bool with_conversion, parse_options const& popts) - : any_impl( - "boost", - flavor, - true, - is_pool, - with_file_io, - with_conversion, - popts) + : any_impl("boost", flavor, true, is_pool, with_file_io, popts) , is_pool_(is_pool) {} }; @@ -579,12 +396,8 @@ public: class boost_impl : public base_boost_impl { public: - boost_impl( - bool is_pool, - bool with_file_io, - bool with_conversion, - parse_options const& popts) - : base_boost_impl("", is_pool, with_file_io, with_conversion, popts) + boost_impl(bool is_pool, bool with_file_io, parse_options const& popts) + : base_boost_impl("", is_pool, with_file_io, popts) {} clock_type::duration @@ -601,7 +414,8 @@ public: p.reset( std::move(sp) ); p.write( fi.text.data(), fi.text.size() ); - maybe_convert_from_value( p.release() ); + auto jv = p.release(); + (void)jv; } return clock_type::now() - start; } @@ -638,8 +452,6 @@ public: auto jv = p.release(); fclose(f); - - maybe_convert_from_value( jv ); } return clock_type::now() - start; } @@ -652,7 +464,6 @@ public: if( is_pool_ ) sp = &mr; value jv = json::parse( fi.text, std::move(sp) ); - data_holder data = construct_data(file_index(), jv); auto const start = clock_type::now(); serializer sr; @@ -660,7 +471,6 @@ public: out.reserve(512); while(repeat--) { - maybe_convert_to_value(data, jv); sr.reset(&jv); out.clear(); for(;;) @@ -756,17 +566,13 @@ class boost_null_impl : public base_boost_impl }; public: - boost_null_impl( - bool with_file_io, bool with_conversion, parse_options const& popts) - : base_boost_impl("null", false, with_file_io, with_conversion, popts) + boost_null_impl(bool with_file_io, parse_options const& popts) + : base_boost_impl("null", false, with_file_io, popts) {} clock_type::duration parse_string(file_item const& fi, std::size_t repeat) const override { - if( file_index() >= 0 ) - return skip(); - auto const start = clock_type::now(); null_parser p( get_parse_options() ); while(repeat--) @@ -783,9 +589,6 @@ public: clock_type::duration parse_file(file_item const& fi, std::size_t repeat) const override { - if( file_index() >= 0 ) - return skip(); - auto const start = clock_type::now(); null_parser p( get_parse_options() ); char s[ BOOST_JSON_STACK_BUFFER_SIZE]; @@ -834,12 +637,8 @@ class boost_simple_impl : public base_boost_impl { public: boost_simple_impl( - bool is_pool, - bool with_file_io, - bool with_conversion, - parse_options const& popts) - : base_boost_impl( - "convenient", is_pool, with_file_io, with_conversion, popts) + bool is_pool, bool with_file_io, parse_options const& popts) + : base_boost_impl("convenient", is_pool, with_file_io, popts) {} clock_type::duration @@ -855,7 +654,7 @@ public: auto jv = json::parse( fi.text, std::move(sp), get_parse_options() ); - maybe_convert_from_value(jv); + (void)jv; } return clock_type::now() - start; } @@ -874,7 +673,7 @@ public: sp = &mr; auto jv = json::parse( is, std::move(sp), get_parse_options() ); - maybe_convert_from_value(jv); + (void)jv; } return clock_type::now() - start; } @@ -887,15 +686,11 @@ public: if( is_pool_ ) sp = &mr; auto jv = json::parse( fi.text, std::move(sp) ); - auto data = construct_data(file_index(), jv); auto const start = clock_type::now(); std::string out; while(repeat--) - { - maybe_convert_to_value(data, jv); out = json::serialize(jv); - } return clock_type::now() - start; } }; @@ -906,12 +701,8 @@ class boost_operator_impl : public base_boost_impl { public: boost_operator_impl( - bool is_pool, - bool with_file_io, - bool with_conversion, - parse_options const& popts) - : base_boost_impl( - "operators", is_pool, with_file_io, with_conversion, popts) + bool is_pool, bool with_file_io, parse_options const& popts) + : base_boost_impl("operators", is_pool, with_file_io, popts) {} clock_type::duration @@ -931,7 +722,6 @@ public: value jv( std::move(sp) ); is.seekg(0); is >> get_parse_options() >> jv; - maybe_convert_from_value(jv); } return clock_type::now() - start; } @@ -952,8 +742,6 @@ public: value jv( std::move(sp) ); is >> get_parse_options() >> jv; - - maybe_convert_from_value(jv); } return clock_type::now() - start; } @@ -967,13 +755,11 @@ public: sp = &mr; auto jv = json::parse( fi.text, std::move(sp) ); - auto data = construct_data(file_index(), jv); auto const start = clock_type::now(); std::string out; while(repeat--) { - maybe_convert_to_value(data, jv); std::ostringstream os; os.exceptions(std::ios::failbit); os << jv; @@ -985,154 +771,6 @@ public: //---------------------------------------------------------- -class boost_direct_impl : public base_boost_impl -{ - struct string_parser - { - file_item const& fi; - parse_options const& opts; - std::size_t& repeat; - - template< class I > - clock_type::duration operator()(I) const - { - auto const start = clock_type::now(); - while(repeat--) - { - using data_type = typename supported_file_at::type; - data_type v; - system::error_code ec; - parser_for p(opts, &v); - - auto const n = p.write_some( - false, fi.text.data(), fi.text.size(), ec ); - if( !ec.failed() && n < fi.text.size() ) - ec = error::extra_data; - if( ec.failed() ) - throw system::system_error( ec ); - } - return clock_type::now() - start; - } - }; - - struct file_parser - { - file_item const& fi; - parse_options const& opts; - std::size_t& repeat; - - template< class I > - clock_type::duration operator()(I) const - { - auto const start = clock_type::now(); - char s[ BOOST_JSON_STACK_BUFFER_SIZE]; - while(repeat--) - { - using data_type = typename supported_file_at::type; - data_type v; - system::error_code ec; - parser_for p(opts, &v); - - FILE* f = fopen(fi.name.data(), "rb"); - - while( true ) - { - std::size_t const sz = fread(s, 1, sizeof(s), f); - if( ferror(f) ) - break; - - p.write_some(true, s, sz, ec); - if( ec.failed() ) - throw system::system_error(ec); - - if( feof(f) ) - break; - } - - if( !p.done() ) - { - p.write_some(false, nullptr, 0, ec); - if( ec.failed() ) - throw system::system_error(ec); - } - - fclose(f); - } - return clock_type::now() - start; - } - }; - - struct string_serializer - { - file_item const& fi; - parse_options const& opts; - std::size_t& repeat; - - template< class I > - clock_type::duration operator()(I) const - { - typename supported_file_at::type v; - json::parse_into(v, fi.text); - - auto const start = clock_type::now(); - serializer sr; - string out; - out.reserve(512); - while(repeat--) - { - sr.reset(&v); - out.clear(); - for(;;) - { - auto const sv = sr.read( - out.end(), out.capacity() - out.size() ); - out.grow( sv.size() ); - if( sr.done() ) - break; - out.reserve( out.capacity() + 1 ); - } - } - return clock_type::now() - start; - } - }; - -public: - boost_direct_impl( - bool with_file_io, bool with_conversion, parse_options const& popts) - : base_boost_impl( - "direct", false, with_file_io, with_conversion, popts) - {} - - clock_type::duration - parse_string(file_item const& fi, std::size_t repeat) const override - { - if( file_index() < 0 ) - return skip(); - return mp11::mp_with_index( - file_index(), string_parser{fi, get_parse_options(), repeat} ); - } - - clock_type::duration - parse_file(file_item const& fi, std::size_t repeat) const override - { - if( file_index() < 0 ) - return skip(); - return mp11::mp_with_index( - file_index(), file_parser{fi, get_parse_options(), repeat} ); - } - - clock_type::duration - serialize_string(file_item const& fi, std::size_t repeat) const override - { - if( file_index() < 0 ) - return skip(); - return mp11::mp_with_index( - file_index(), string_serializer{fi, get_parse_options(), repeat} ); - } -}; - -//---------------------------------------------------------- - #ifdef BOOST_JSON_HAS_RAPIDJSON template struct rapidjson_impl : public any_impl @@ -1153,23 +791,19 @@ struct rapidjson_impl : public any_impl return opts; } - rapidjson_impl(bool with_file_io, bool with_conversion) + rapidjson_impl(bool with_file_io) : any_impl( "rapidjson", "", false, std::is_same::value, with_file_io, - with_conversion, make_parse_options() ) {} clock_type::duration parse_string(file_item const& fi, std::size_t repeat) const override { - if( file_index() >= 0 ) - return skip(); - using namespace rapidjson; auto const start = clock_type::now(); while(repeat--) @@ -1185,9 +819,6 @@ struct rapidjson_impl : public any_impl clock_type::duration parse_file(file_item const& fi, std::size_t repeat) const override { - if( file_index() >= 0 ) - return skip(); - using namespace rapidjson; auto const start = clock_type::now(); @@ -1211,9 +842,6 @@ struct rapidjson_impl : public any_impl clock_type::duration serialize_string(file_item const& fi, std::size_t repeat) const override { - if( file_index() >= 0 ) - return skip(); - using namespace rapidjson; Allocator alloc; GenericDocument, Allocator> d(&alloc); @@ -1237,23 +865,19 @@ struct rapidjson_impl : public any_impl #ifdef BOOST_JSON_HAS_NLOHMANN_JSON struct nlohmann_impl : public any_impl { - nlohmann_impl(bool with_file_io, bool with_conversion) + nlohmann_impl(bool with_file_io) : any_impl( "nlohmann", "", false, false, with_file_io, - with_conversion, parse_options() ) {} clock_type::duration parse_string(file_item const& fi, std::size_t repeat) const override { - if( file_index() >= 0 ) - return skip(); - auto const start = clock_type::now(); while(repeat--) { @@ -1265,9 +889,6 @@ struct nlohmann_impl : public any_impl clock_type::duration parse_file(file_item const& fi, std::size_t repeat) const override { - if( file_index() >= 0 ) - return skip(); - auto const start = clock_type::now(); char* s = new char[ fi.text.size() ]; std::unique_ptr holder(s); @@ -1287,9 +908,6 @@ struct nlohmann_impl : public any_impl clock_type::duration serialize_string(file_item const& fi, std::size_t repeat) const override { - if( file_index() >= 0 ) - return skip(); - auto jv = nlohmann::json::parse( fi.text.begin(), fi.text.end() ); auto const start = clock_type::now(); @@ -1353,17 +971,13 @@ static bool parse_option( char const* s ) case 'f': s_file_io = s; break; - - case 'c': - s_conversion = s; - break; } return true; } bool add_impl( - impl_list & vi, char kind, char alloc, char io, char num, char conv) + impl_list & vi, char kind, char alloc, char io, char num) { parse_options popts; switch(num) @@ -1382,36 +996,30 @@ bool add_impl( } bool const with_file_io = io == 'y'; bool const is_pool = alloc == 'p'; - bool const with_conversion = conv == 'y'; impl_ptr impl; switch( kind ) { case 'b': impl.reset( - new boost_impl(is_pool, with_file_io, with_conversion, popts) ); + new boost_impl(is_pool, with_file_io, popts) ); break; case 'u': impl.reset( - new boost_null_impl(with_file_io, with_conversion, popts) ); + new boost_null_impl(with_file_io, popts) ); break; case 's': impl.reset( new boost_simple_impl( - is_pool, with_file_io, with_conversion, popts) ); + is_pool, with_file_io, popts) ); break; case 'o': impl.reset( new boost_operator_impl( - is_pool, with_file_io, with_conversion, popts) ); - break; - - case 'd': - impl.reset( - new boost_direct_impl(with_file_io, with_conversion, popts) ); + is_pool, with_file_io, popts) ); break; #ifdef BOOST_JSON_HAS_RAPIDJSON @@ -1421,31 +1029,27 @@ bool add_impl( using Allocator = RAPIDJSON_DEFAULT_ALLOCATOR; if(popts.numbers == number_precision::precise) impl.reset( - new rapidjson_impl( - with_file_io, with_conversion) ); + new rapidjson_impl(with_file_io) ); else impl.reset( - new rapidjson_impl( - with_file_io, with_conversion) ); + new rapidjson_impl(with_file_io) ); } else { using Allocator = rapidjson::CrtAllocator; if(popts.numbers == number_precision::precise) impl.reset( - new rapidjson_impl( - with_file_io, with_conversion) ); + new rapidjson_impl(with_file_io) ); else impl.reset( - new rapidjson_impl( - with_file_io, with_conversion) ); + new rapidjson_impl( with_file_io) ); } break; #endif // BOOST_JSON_HAS_RAPIDJSON #ifdef BOOST_JSON_HAS_NLOHMANN_JSON case 'n': - impl.reset( new nlohmann_impl(with_file_io, with_conversion) ); + impl.reset( new nlohmann_impl(with_file_io) ); break; #endif // BOOST_JSON_HAS_NLOHMANN_JSON @@ -1495,7 +1099,6 @@ main( " (u: Boost.JSON, null parser)\n" " (s: Boost.JSON, convenient functions)\n" " (o: Boost.JSON, stream operators)\n" - " (d: Boost.JSON, direct conversion)\n" #ifdef BOOST_JSON_HAS_RAPIDJSON " (r: RapidJSON)\n" #endif // BOOST_JSON_HAS_RAPIDJSON @@ -1518,10 +1121,6 @@ main( " (y: yes)\n" " (n: no)\n" " (default no)\n" - " -c:[y][n] Convert to user-defined type\n" - " (y: yes)\n" - " (n: no)\n" - " (default no)\n" ; return 4; @@ -1552,8 +1151,7 @@ main( for( char alloc: s_alloc ) for( char num: s_num_mode ) for( char io: s_file_io ) - for( char conv: s_conversion ) - add_impl( vi, impl, alloc, io, num, conv ); + add_impl( vi, impl, alloc, io, num ); // remove duplicate implementations std::sort(