From a8f711a2f18e8fae0efcd05aff5b673dca5fa0b4 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 18 Jan 2018 21:57:21 +0100 Subject: [PATCH] :heavy_plus_sign: using Google Benchmark #921 --- README.md | 3 +- benchmarks/CMakeLists.txt | 26 + benchmarks/Makefile | 21 - benchmarks/README.md | 3 - .../{files => data}/jeopardy/jeopardy.json | 0 .../nativejson-benchmark/canada.json | 0 .../nativejson-benchmark/citm_catalog.json | 0 .../nativejson-benchmark/twitter.json | 0 benchmarks/files/numbers/generate.py | 25 - benchmarks/src/benchmarks.cpp | 214 ++- benchmarks/src/benchmarks_simple.cpp | 158 -- benchmarks/thirdparty/benchpress/LICENSE | 21 - .../thirdparty/benchpress/benchpress.hpp | 401 ----- benchmarks/thirdparty/cxxopts/LICENSE | 19 - benchmarks/thirdparty/cxxopts/cxxopts.hpp | 1312 ----------------- develop/json.hpp | 2 +- src/json.hpp | 2 +- test/src/unit-inspection.cpp | 4 +- 18 files changed, 125 insertions(+), 2086 deletions(-) create mode 100644 benchmarks/CMakeLists.txt delete mode 100644 benchmarks/Makefile delete mode 100644 benchmarks/README.md rename benchmarks/{files => data}/jeopardy/jeopardy.json (100%) rename benchmarks/{files => data}/nativejson-benchmark/canada.json (100%) rename benchmarks/{files => data}/nativejson-benchmark/citm_catalog.json (100%) rename benchmarks/{files => data}/nativejson-benchmark/twitter.json (100%) delete mode 100755 benchmarks/files/numbers/generate.py delete mode 100644 benchmarks/src/benchmarks_simple.cpp delete mode 100644 benchmarks/thirdparty/benchpress/LICENSE delete mode 100644 benchmarks/thirdparty/benchpress/benchpress.hpp delete mode 100644 benchmarks/thirdparty/cxxopts/LICENSE delete mode 100644 benchmarks/thirdparty/cxxopts/cxxopts.hpp diff --git a/README.md b/README.md index d5935755..e4c71481 100644 --- a/README.md +++ b/README.md @@ -935,7 +935,6 @@ The library itself contains of a single header file licensed under the MIT licen - [**American fuzzy lop**](http://lcamtuf.coredump.cx/afl/) for fuzz testing - [**AppVeyor**](https://www.appveyor.com) for [continuous integration](https://ci.appveyor.com/project/nlohmann/json) on Windows - [**Artistic Style**](http://astyle.sourceforge.net) for automatic source code identation -- [**benchpress**](https://github.com/sbs-ableton/benchpress) to benchmark the code - [**Catch**](https://github.com/philsquared/Catch) for the unit tests - [**Clang**](http://clang.llvm.org) for compilation with code sanitizers - [**Cmake**](https://cmake.org) for build automation @@ -943,10 +942,10 @@ The library itself contains of a single header file licensed under the MIT licen - [**Coveralls**](https://coveralls.io) to measure [code coverage](https://coveralls.io/github/nlohmann/json) - [**Coverity Scan**](https://scan.coverity.com) for [static analysis](https://scan.coverity.com/projects/nlohmann-json) - [**cppcheck**](http://cppcheck.sourceforge.net) for static analysis -- [**cxxopts**](https://github.com/jarro2783/cxxopts) to let benchpress parse command-line parameters - [**Doxygen**](http://www.stack.nl/~dimitri/doxygen/) to generate [documentation](https://nlohmann.github.io/json/) - [**git-update-ghpages**](https://github.com/rstacruz/git-update-ghpages) to upload the documentation to gh-pages - [**GitHub Changelog Generator**](https://github.com/skywinder/github-changelog-generator) to generate the [ChangeLog](https://github.com/nlohmann/json/blob/develop/ChangeLog.md) +- [**Google Benchmark**]https://github.com/google/benchmark) to implement the benchmarks - [**libFuzzer**](http://llvm.org/docs/LibFuzzer.html) to implement fuzz testing for OSS-Fuzz - [**OSS-Fuzz**](https://github.com/google/oss-fuzz) for continuous fuzz testing of the library - [**Probot**](https://probot.github.io) for automating maintainer tasks such as closing stale issues, requesting missing information, or detecting toxic comments. diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt new file mode 100644 index 00000000..f614978e --- /dev/null +++ b/benchmarks/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.0) +project(JSON_Benchmarks LANGUAGES CXX) + +# set compiler flags +if((CMAKE_CXX_COMPILER_ID MATCHES GNU) OR (CMAKE_CXX_COMPILER_ID MATCHES Clang)) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -flto -DNDEBUG -O3") +endif() + +# configure Google Benchmarks +set(BENCHMARK_ENABLE_TESTING OFF CACHE INTERNAL "" FORCE) +add_subdirectory(thirdparty/benchmark) + +# header directories +include_directories(thirdparty) +include_directories(${CMAKE_SOURCE_DIR}/src) + +# copy test files to build folder +file(COPY ${CMAKE_SOURCE_DIR}/data DESTINATION .) +file(COPY ${CMAKE_SOURCE_DIR}/../test/data/regression/floats.json + ${CMAKE_SOURCE_DIR}/../test/data/regression/unsigned_ints.json + ${CMAKE_SOURCE_DIR}/../test/data/regression/signed_ints.json + DESTINATION data/numbers) + +# benchmark binary +add_executable(json_benchmarks src/benchmarks.cpp) +target_link_libraries(json_benchmarks benchmark ${CMAKE_THREAD_LIBS_INIT}) diff --git a/benchmarks/Makefile b/benchmarks/Makefile deleted file mode 100644 index ef2de8a3..00000000 --- a/benchmarks/Makefile +++ /dev/null @@ -1,21 +0,0 @@ - -# -# Build/run json.hpp benchmarks, eg. CXX=g++-7 make -# -# The existing json_benchmarks did not allow optimization under some compilers -# -all: json_benchmarks json_benchmarks_simple number_jsons - bash -c 'time ./json_benchmarks' - bash -c 'time ./json_benchmarks_simple' - -json_benchmarks: src/benchmarks.cpp ../src/json.hpp - $(CXX) -std=c++11 -pthread $(CXXFLAGS) -DNDEBUG -O3 -flto -I thirdparty/benchpress -I thirdparty/cxxopts -I../src src/benchmarks.cpp $(LDFLAGS) -o $@ - -json_benchmarks_simple: src/benchmarks_simple.cpp ../src/json.hpp - $(CXX) -std=c++11 $(CXXFLAGS) -DNDEBUG -O3 -flto -I../src $(<) $(LDFLAGS) -o $@ - -number_jsons: - (test -e files/numbers/floats.json -a -e files/numbers/signed_ints.json -a -e files/numbers/unsigned_ints.json) || (cd files/numbers ; python generate.py) - -clean: - rm -f json_benchmarks json_benchmarks_simple files/numbers/*.json diff --git a/benchmarks/README.md b/benchmarks/README.md deleted file mode 100644 index 1d3582b4..00000000 --- a/benchmarks/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Bechmarks - -Run `make` to compile and run a small set of benchmarks. diff --git a/benchmarks/files/jeopardy/jeopardy.json b/benchmarks/data/jeopardy/jeopardy.json similarity index 100% rename from benchmarks/files/jeopardy/jeopardy.json rename to benchmarks/data/jeopardy/jeopardy.json diff --git a/benchmarks/files/nativejson-benchmark/canada.json b/benchmarks/data/nativejson-benchmark/canada.json similarity index 100% rename from benchmarks/files/nativejson-benchmark/canada.json rename to benchmarks/data/nativejson-benchmark/canada.json diff --git a/benchmarks/files/nativejson-benchmark/citm_catalog.json b/benchmarks/data/nativejson-benchmark/citm_catalog.json similarity index 100% rename from benchmarks/files/nativejson-benchmark/citm_catalog.json rename to benchmarks/data/nativejson-benchmark/citm_catalog.json diff --git a/benchmarks/files/nativejson-benchmark/twitter.json b/benchmarks/data/nativejson-benchmark/twitter.json similarity index 100% rename from benchmarks/files/nativejson-benchmark/twitter.json rename to benchmarks/data/nativejson-benchmark/twitter.json diff --git a/benchmarks/files/numbers/generate.py b/benchmarks/files/numbers/generate.py deleted file mode 100755 index 714ee3a1..00000000 --- a/benchmarks/files/numbers/generate.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python - -import json -import random -import sys - -random.seed(0) - -# floats -result_floats = [] -for x in range(0, 1000000): - result_floats.append(random.uniform(-100000000.0, 100000000.0)) -json.dump(result_floats, open("floats.json", "w"), indent=2) - -# unsigned integers -result_uints = [] -for x in range(0, 1000000): - result_uints.append(random.randint(0, 18446744073709551615)) -json.dump(result_uints, open("unsigned_ints.json", "w"), indent=2) - -# signed integers -result_sints = [] -for x in range(0, 1000000): - result_sints.append(random.randint(-9223372036854775808, 9223372036854775807)) -json.dump(result_sints, open("signed_ints.json", "w"), indent=2) diff --git a/benchmarks/src/benchmarks.cpp b/benchmarks/src/benchmarks.cpp index a76c3783..6dd470d3 100644 --- a/benchmarks/src/benchmarks.cpp +++ b/benchmarks/src/benchmarks.cpp @@ -1,132 +1,106 @@ -#define BENCHPRESS_CONFIG_MAIN - +#include "benchmark/benchmark.h" +#include "json.hpp" #include -#include -#include -#include -#include -#include using json = nlohmann::json; -struct StartUp +////////////////////////////////////////////////////////////////////////////// +// parse JSON from file +////////////////////////////////////////////////////////////////////////////// + +static void ParseFile(benchmark::State& state, const char* filename) { - StartUp() + while (state.KeepRunning()) { -#ifndef __llvm__ - // pin thread to a single CPU - cpu_set_t cpuset; - pthread_t thread; - thread = pthread_self(); - CPU_ZERO(&cpuset); - CPU_SET(std::thread::hardware_concurrency() - 1, &cpuset); - pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpuset); -#endif + state.PauseTiming(); + auto* f = new std::ifstream(filename); + auto* j = new json(); + state.ResumeTiming(); + + *j = json::parse(*f); + + state.PauseTiming(); + delete f; + delete j; + state.ResumeTiming(); } -}; -StartUp startup; -enum class EMode { input, output_no_indent, output_with_indent }; - -static void bench(benchpress::context& ctx, - const std::string& in_path, - const EMode mode) -{ - // using string streams for benchmarking to factor-out cold-cache disk - // access. -#if defined( FROMFILE ) - std::ifstream istr; - { - istr.open( in_path, std::ifstream::in ); - - // read the stream once - json j; - istr >> j; - // clear flags and rewind - istr.clear(); - istr.seekg(0); - } -#else - std::stringstream istr; - { - // read file into string stream - std::ifstream input_file(in_path); - istr << input_file.rdbuf(); - input_file.close(); - - // read the stream once - json j; - istr >> j; - // clear flags and rewind - istr.clear(); - istr.seekg(0); - } -#endif - - switch (mode) - { - // benchmarking input - case EMode::input: - { - ctx.reset_timer(); - - for (size_t i = 0; i < ctx.num_iterations(); ++i) - { - // clear flags and rewind - istr.clear(); - istr.seekg(0); - json j; - istr >> j; - } - - break; - } - - // benchmarking output - case EMode::output_no_indent: - case EMode::output_with_indent: - { - // create JSON value from input - json j; - istr >> j; - std::stringstream ostr; - - ctx.reset_timer(); - for (size_t i = 0; i < ctx.num_iterations(); ++i) - { - if (mode == EMode::output_no_indent) - { - ostr << j; - } - else - { - ostr << std::setw(4) << j; - } - - // reset data - ostr.str(std::string()); - } - - break; - } - } + std::ifstream file(filename, std::ios::binary | std::ios::ate); + state.SetBytesProcessed(state.iterations() * file.tellg()); } +BENCHMARK_CAPTURE(ParseFile, jeopardy, "data/jeopardy/jeopardy.json"); +BENCHMARK_CAPTURE(ParseFile, canada, "data/nativejson-benchmark/canada.json"); +BENCHMARK_CAPTURE(ParseFile, citm_catalog, "data/nativejson-benchmark/citm_catalog.json"); +BENCHMARK_CAPTURE(ParseFile, twitter, "data/nativejson-benchmark/twitter.json"); +BENCHMARK_CAPTURE(ParseFile, floats, "data/numbers/floats.json"); +BENCHMARK_CAPTURE(ParseFile, signed_ints, "data/numbers/signed_ints.json"); +BENCHMARK_CAPTURE(ParseFile, unsigned_ints, "data/numbers/unsigned_ints.json"); -#define BENCHMARK_I(mode, title, in_path) \ - BENCHMARK((title), [](benchpress::context* ctx) \ - { \ - bench(*ctx, (in_path), (mode)); \ - }) -BENCHMARK_I(EMode::input, "parse jeopardy.json", "files/jeopardy/jeopardy.json"); -BENCHMARK_I(EMode::input, "parse canada.json", "files/nativejson-benchmark/canada.json"); -BENCHMARK_I(EMode::input, "parse citm_catalog.json", "files/nativejson-benchmark/citm_catalog.json"); -BENCHMARK_I(EMode::input, "parse twitter.json", "files/nativejson-benchmark/twitter.json"); -BENCHMARK_I(EMode::input, "parse numbers/floats.json", "files/numbers/floats.json"); -BENCHMARK_I(EMode::input, "parse numbers/signed_ints.json", "files/numbers/signed_ints.json"); -BENCHMARK_I(EMode::input, "parse numbers/unsigned_ints.json", "files/numbers/unsigned_ints.json"); +////////////////////////////////////////////////////////////////////////////// +// parse JSON from string +////////////////////////////////////////////////////////////////////////////// -BENCHMARK_I(EMode::output_no_indent, "dump jeopardy.json", "files/jeopardy/jeopardy.json"); -BENCHMARK_I(EMode::output_with_indent, "dump jeopardy.json with indent", "files/jeopardy/jeopardy.json"); -BENCHMARK_I(EMode::output_no_indent, "dump numbers/floats.json", "files/numbers/floats.json"); -BENCHMARK_I(EMode::output_no_indent, "dump numbers/signed_ints.json", "files/numbers/signed_ints.json"); +static void ParseString(benchmark::State& state, const char* filename) +{ + std::ifstream f(filename); + std::string str((std::istreambuf_iterator(f)), std::istreambuf_iterator()); + + while (state.KeepRunning()) + { + state.PauseTiming(); + auto* j = new json(); + state.ResumeTiming(); + + *j = json::parse(str); + + state.PauseTiming(); + delete j; + state.ResumeTiming(); + } + + state.SetBytesProcessed(state.iterations() * str.size()); +} +BENCHMARK_CAPTURE(ParseString, jeopardy, "data/jeopardy/jeopardy.json"); +BENCHMARK_CAPTURE(ParseString, canada, "data/nativejson-benchmark/canada.json"); +BENCHMARK_CAPTURE(ParseString, citm_catalog, "data/nativejson-benchmark/citm_catalog.json"); +BENCHMARK_CAPTURE(ParseString, twitter, "data/nativejson-benchmark/twitter.json"); +BENCHMARK_CAPTURE(ParseString, floats, "data/numbers/floats.json"); +BENCHMARK_CAPTURE(ParseString, signed_ints, "data/numbers/signed_ints.json"); +BENCHMARK_CAPTURE(ParseString, unsigned_ints, "data/numbers/unsigned_ints.json"); + + +////////////////////////////////////////////////////////////////////////////// +// serialize JSON +////////////////////////////////////////////////////////////////////////////// + +static void Dump(benchmark::State& state, const char* filename, int indent) +{ + std::ifstream f(filename); + std::string str((std::istreambuf_iterator(f)), std::istreambuf_iterator()); + json j = json::parse(str); + + while (state.KeepRunning()) + { + j.dump(indent); + } + + state.SetBytesProcessed(state.iterations() * j.dump(indent).size()); +} +BENCHMARK_CAPTURE(Dump, jeopardy / -, "data/jeopardy/jeopardy.json", -1); +BENCHMARK_CAPTURE(Dump, jeopardy / 4, "data/jeopardy/jeopardy.json", 4); +BENCHMARK_CAPTURE(Dump, canada / -, "data/nativejson-benchmark/canada.json", -1); +BENCHMARK_CAPTURE(Dump, canada / 4, "data/nativejson-benchmark/canada.json", 4); +BENCHMARK_CAPTURE(Dump, citm_catalog / -, "data/nativejson-benchmark/citm_catalog.json", -1); +BENCHMARK_CAPTURE(Dump, citm_catalog / 4, "data/nativejson-benchmark/citm_catalog.json", 4); +BENCHMARK_CAPTURE(Dump, twitter / -, "data/nativejson-benchmark/twitter.json", -1); +BENCHMARK_CAPTURE(Dump, twitter / 4, "data/nativejson-benchmark/twitter.json", 4); +BENCHMARK_CAPTURE(Dump, floats / -, "data/numbers/floats.json", -1); +BENCHMARK_CAPTURE(Dump, floats / 4, "data/numbers/floats.json", 4); +BENCHMARK_CAPTURE(Dump, signed_ints / -, "data/numbers/signed_ints.json", -1); +BENCHMARK_CAPTURE(Dump, signed_ints / 4, "data/numbers/signed_ints.json", 4); +BENCHMARK_CAPTURE(Dump, unsigned_ints / -, "data/numbers/unsigned_ints.json", -1); +BENCHMARK_CAPTURE(Dump, unsigned_ints / 4, "data/numbers/unsigned_ints.json", 4); + + +BENCHMARK_MAIN(); diff --git a/benchmarks/src/benchmarks_simple.cpp b/benchmarks/src/benchmarks_simple.cpp deleted file mode 100644 index 4fad680a..00000000 --- a/benchmarks/src/benchmarks_simple.cpp +++ /dev/null @@ -1,158 +0,0 @@ -// -// benchmarks_simple.cpp -- a less complex version of benchmarks.cpp, that better reflects actual performance -// -// For some reason, the complexity of benchmarks.cpp doesn't allow -// the compiler to optimize code using json.hpp effectively. The -// exact same tests, with the use of benchpress and cxxopts produces -// much faster code, at least under g++. -// -#include -#include -#include -#include -#include - -#include - -using json = nlohmann::json; - -enum class EMode { input, output, indent }; - -static double bench(const EMode mode, size_t iters, const std::string& in_path ) -{ - // using string streams for benchmarking to factor-out cold-cache disk - // access. Define FROMFILE to use file I/O instead. -#if defined( FROMFILE ) - std::ifstream istr; - { - istr.open( in_path, std::ifstream::in ); - - // read the stream once - json j; - istr >> j; - // clear flags and rewind - istr.clear(); - istr.seekg(0); - } -#else - std::stringstream istr; - { - // read file into string stream - std::ifstream input_file(in_path); - istr << input_file.rdbuf(); - input_file.close(); - - // read the stream once - json j; - istr >> j; - // clear flags and rewind - istr.clear(); - istr.seekg(0); - } -#endif - double tps = 0; - switch (mode) - { - // benchmarking input - case EMode::input: - { - auto start = std::chrono::system_clock::now(); - for (size_t i = 0; i < iters; ++i) - { - // clear flags and rewind - istr.clear(); - istr.seekg(0); - json j; - istr >> j; - } - auto ended = std::chrono::system_clock::now(); - tps = 1.0 / std::chrono::duration( ended - start ).count(); - break; - } - - // benchmarking output - case EMode::output: - case EMode::indent: - { - // create JSON value from input - json j; - istr >> j; - std::stringstream ostr; - - auto start = std::chrono::system_clock::now(); - for (size_t i = 0; i < iters; ++i) - { - if (mode == EMode::indent) - { - ostr << j; - } - else - { - ostr << std::setw(4) << j; - } - - // reset data - ostr.str(std::string()); - } - auto ended = std::chrono::system_clock::now(); - tps = 1.0 / std::chrono::duration( ended - start ).count(); - - break; - } - } - return tps; -} - -template -struct average { - T _sum { 0 }; - size_t _count { 0 }; - T operator+=( const T &val_ ) { _sum += val_; +_count++; return val_; } - operator T() { return _sum / _count; } -}; - -// Execute each test approximately enough times to get near 1 -// transaction per second, and compute the average; a single aggregate -// number that gives a performance metric representing both parsing -// and output. - -int main( int, char ** ) -{ - std::list> tests { - { "parse jeopardy.json", EMode::input, 2, "files/jeopardy/jeopardy.json" }, - { "parse canada.json", EMode::input, 30, "files/nativejson-benchmark/canada.json" }, - { "parse citm_catalog.json", EMode::input, 120, "files/nativejson-benchmark/citm_catalog.json" }, - { "parse twitter.json", EMode::input, 225, "files/nativejson-benchmark/twitter.json" }, - { "parse floats.json", EMode::input, 5, "files/numbers/floats.json" }, - { "parse signed_ints.json", EMode::input, 6, "files/numbers/signed_ints.json" }, - { "parse unsigned_ints.json", EMode::input, 6, "files/numbers/unsigned_ints.json" }, - { "dump jeopardy.json", EMode::output, 5, "files/jeopardy/jeopardy.json" }, - { "dump jeopardy.json w/ind.", EMode::indent, 5, "files/jeopardy/jeopardy.json" }, - { "dump floats.json", EMode::output, 2, "files/numbers/floats.json" }, - { "dump signed_ints.json", EMode::output, 20, "files/numbers/signed_ints.json" }, - }; - - average avg; - for ( auto t : tests ) { - std::string name, path; - EMode mode; - size_t iters; - std::tie(name, mode, iters, path) = t; - auto tps = bench( mode, iters, path ); - avg += tps; - std::cout - << std::left - << std::setw( 30 ) << name - << std::right - << " x " << std::setw( 3 ) << iters - << std::left - << " == " << std::setw( 10 ) << tps - << std::right - << " TPS, " << std::setw( 8 ) << std::round( tps * 1e6 / iters ) - << " ms/op" - << std::endl; - } - std::cout << std::setw( 40 ) << "" << std::string( 10, '-' ) << std::endl; - std::cout << std::setw( 40 ) << "" << std::setw( 10 ) << std::left << avg << " TPS Average" << std::endl; - return 0; -} diff --git a/benchmarks/thirdparty/benchpress/LICENSE b/benchmarks/thirdparty/benchpress/LICENSE deleted file mode 100644 index 282efc46..00000000 --- a/benchmarks/thirdparty/benchpress/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Christopher Gilbert - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/benchmarks/thirdparty/benchpress/benchpress.hpp b/benchmarks/thirdparty/benchpress/benchpress.hpp deleted file mode 100644 index d68e4aff..00000000 --- a/benchmarks/thirdparty/benchpress/benchpress.hpp +++ /dev/null @@ -1,401 +0,0 @@ -/* -* Copyright (C) 2015 Christopher Gilbert. -* -* Permission is hereby granted, free of charge, to any person obtaining a copy -* of this software and associated documentation files (the "Software"), to deal -* in the Software without restriction, including without limitation the rights -* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the Software is -* furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in all -* copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -* SOFTWARE. -*/ -#ifndef BENCHPRESS_HPP -#define BENCHPRESS_HPP - -#include // max, min -#include // atomic_intmax_t -#include // high_resolution_timer, duration -#include // function -#include // setw -#include // cout -#include // regex, regex_match -#include // stringstream -#include // string -#include // thread -#include // vector - -namespace benchpress { - -/* - * The options class encapsulates all options for running benchmarks. - * - * When including benchpress, a main function can be emitted which includes a command-line parser for building an - * options object. However from time-to-time it may be necessary for the developer to have to build their own main - * stub and construct the options object manually. - * - * options opts; - * opts - * .bench(".*") - * .benchtime(1) - * .cpu(4); - */ -class options { - std::string d_bench; - size_t d_benchtime; - size_t d_cpu; -public: - options() - : d_bench(".*") - , d_benchtime(1) - , d_cpu(std::thread::hardware_concurrency()) - {} - options& bench(const std::string& bench) { - d_bench = bench; - return *this; - } - options& benchtime(size_t benchtime) { - d_benchtime = benchtime; - return *this; - } - options& cpu(size_t cpu) { - d_cpu = cpu; - return *this; - } - std::string get_bench() const { - return d_bench; - } - size_t get_benchtime() const { - return d_benchtime; - } - size_t get_cpu() const { - return d_cpu; - } -}; - -class context; - -/* - * The benchmark_info class is used to store a function name / pointer pair. - * - * benchmark_info bi("example", [](benchpress::context* b) { - * // benchmark function - * }); - */ -class benchmark_info { - std::string d_name; - std::function d_func; - -public: - benchmark_info(std::string name, std::function func) - : d_name(name) - , d_func(func) - {} - - std::string get_name() const { return d_name; } - std::function get_func() const { return d_func; } -}; - -/* - * The registration class is responsible for providing a single global point of reference for registering - * benchmark functions. - * - * registration::get_ptr()->register_benchmark(info); - */ -class registration { - static registration* d_this; - std::vector d_benchmarks; - -public: - static registration* get_ptr() { - if (nullptr == d_this) { - d_this = new registration(); - } - return d_this; - } - - void register_benchmark(benchmark_info& info) { - d_benchmarks.push_back(info); - } - - std::vector get_benchmarks() { return d_benchmarks; } -}; - -/* - * The auto_register class is a helper used to register benchmarks. - */ -class auto_register { -public: - auto_register(const std::string& name, std::function func) { - benchmark_info info(name, func); - registration::get_ptr()->register_benchmark(info); - } -}; - -#define CONCAT(x, y) x ## y -#define CONCAT2(x, y) CONCAT(x, y) - -// The BENCHMARK macro is a helper for creating benchmark functions and automatically registering them with the -// registration class. -#define BENCHMARK(x, f) benchpress::auto_register CONCAT2(register_, __LINE__)((x), (f)); - -// This macro will prevent the compiler from removing a redundant code path which has no side-effects. -#define DISABLE_REDUNDANT_CODE_OPT() { asm(""); } - -/* - * The result class is responsible for producing a printable string representation of a benchmark run. - */ -class result { - size_t d_num_iterations; - std::chrono::nanoseconds d_duration; - size_t d_num_bytes; - -public: - result(size_t num_iterations, std::chrono::nanoseconds duration, size_t num_bytes) - : d_num_iterations(num_iterations) - , d_duration(duration) - , d_num_bytes(num_bytes) - {} - - size_t get_ns_per_op() const { - if (d_num_iterations <= 0) { - return 0; - } - return d_duration.count() / d_num_iterations; - } - - double get_mb_per_s() const { - if (d_num_iterations <= 0 || d_duration.count() <= 0 || d_num_bytes <= 0) { - return 0; - } - return ((double(d_num_bytes) * double(d_num_iterations) / double(1e6)) / - double(std::chrono::duration_cast(d_duration).count())); - } - - std::string to_string() const { - std::stringstream tmp; - tmp << std::setw(12) << std::right << d_num_iterations; - size_t npo = get_ns_per_op(); - tmp << std::setw(12) << std::right << npo << std::setw(0) << " ns/op"; - double mbs = get_mb_per_s(); - if (mbs > 0.0) { - tmp << std::setw(12) << std::right << mbs << std::setw(0) << " MB/s"; - } - return std::string(tmp.str()); - } -}; - -/* - * The parallel_context class is responsible for providing a thread-safe context for parallel benchmark code. - */ -class parallel_context { - std::atomic_intmax_t d_num_iterations; -public: - parallel_context(size_t num_iterations) - : d_num_iterations(num_iterations) - {} - - bool next() { - return (d_num_iterations.fetch_sub(1) > 0); - } -}; - -/* - * The context class is responsible for providing an interface for capturing benchmark metrics to benchmark functions. - */ -class context { - bool d_timer_on; - std::chrono::high_resolution_clock::time_point d_start; - std::chrono::nanoseconds d_duration; - std::chrono::seconds d_benchtime; - size_t d_num_iterations; - size_t d_num_threads; - size_t d_num_bytes; - benchmark_info d_benchmark; - -public: - context(const benchmark_info& info, const options& opts) - : d_timer_on(false) - , d_start() - , d_duration() - , d_benchtime(std::chrono::seconds(opts.get_benchtime())) - , d_num_iterations(1) - , d_num_threads(opts.get_cpu()) - , d_num_bytes(0) - , d_benchmark(info) - {} - - size_t num_iterations() const { return d_num_iterations; } - - void set_num_threads(size_t n) { d_num_threads = n; } - size_t num_threads() const { return d_num_threads; } - - void start_timer() { - if (!d_timer_on) { - d_start = std::chrono::high_resolution_clock::now(); - d_timer_on = true; - } - } - void stop_timer() { - if (d_timer_on) { - d_duration += std::chrono::high_resolution_clock::now() - d_start; - d_timer_on = false; - } - } - void reset_timer() { - if (d_timer_on) { - d_start = std::chrono::high_resolution_clock::now(); - } - d_duration = std::chrono::nanoseconds::zero(); - } - - void set_bytes(int64_t bytes) { d_num_bytes = bytes; } - - size_t get_ns_per_op() { - if (d_num_iterations <= 0) { - return 0; - } - return d_duration.count() / d_num_iterations; - } - - void run_n(size_t n) { - d_num_iterations = n; - reset_timer(); - start_timer(); - d_benchmark.get_func()(this); - stop_timer(); - } - - void run_parallel(std::function f) { - parallel_context pc(d_num_iterations); - std::vector threads; - for (size_t i = 0; i < d_num_threads; ++i) { - threads.push_back(std::thread([&pc,&f]() -> void { - f(&pc); - })); - } - for(auto& thread : threads){ - thread.join(); - } - } - - result run() { - size_t n = 1; - run_n(n); - while (d_duration < d_benchtime && n < 1e9) { - size_t last = n; - if (get_ns_per_op() == 0) { - n = 1e9; - } else { - n = d_duration.count() / get_ns_per_op(); - } - n = std::max(std::min(n+n/2, 100*last), last+1); - n = round_up(n); - run_n(n); - } - return result(n, d_duration, d_num_bytes); - } - -private: - template - T round_down_10(T n) { - int tens = 0; - while (n > 10) { - n /= 10; - tens++; - } - int result = 1; - for (int i = 0; i < tens; ++i) { - result *= 10; - } - return result; - } - - template - T round_up(T n) { - T base = round_down_10(n); - if (n < (2 * base)) { - return 2 * base; - } - if (n < (5 * base)) { - return 5 * base; - } - return 10 * base; - } -}; - -/* - * The run_benchmarks function will run the registered benchmarks. - */ -void run_benchmarks(const options& opts) { - std::regex match_r(opts.get_bench()); - auto benchmarks = registration::get_ptr()->get_benchmarks(); - for (auto& info : benchmarks) { - if (std::regex_match(info.get_name(), match_r)) { - context c(info, opts); - auto r = c.run(); - std::cout << std::setw(35) << std::left << info.get_name() << r.to_string() << std::endl; - } - } -} - -} // namespace benchpress - -/* - * If BENCHPRESS_CONFIG_MAIN is defined when the file is included then a main function will be emitted which provides a - * command-line parser and then executes run_benchmarks. - */ -#ifdef BENCHPRESS_CONFIG_MAIN -#include "cxxopts.hpp" -benchpress::registration* benchpress::registration::d_this; -int main(int argc, char** argv) { - std::chrono::high_resolution_clock::time_point bp_start = std::chrono::high_resolution_clock::now(); - benchpress::options bench_opts; - try { - cxxopts::Options cmd_opts(argv[0], " - command line options"); - cmd_opts.add_options() - ("bench", "run benchmarks matching the regular expression", cxxopts::value() - ->default_value(".*")) - ("benchtime", "run enough iterations of each benchmark to take t seconds", cxxopts::value() - ->default_value("1")) - ("cpu", "specify the number of threads to use for parallel benchmarks", cxxopts::value() - ->default_value(std::to_string(std::thread::hardware_concurrency()))) - ("help", "print help") - ; - cmd_opts.parse(argc, argv); - if (cmd_opts.count("help")) { - std::cout << cmd_opts.help({""}) << std::endl; - exit(0); - } - if (cmd_opts.count("bench")) { - bench_opts.bench(cmd_opts["bench"].as()); - } - if (cmd_opts.count("benchtime")) { - bench_opts.benchtime(cmd_opts["benchtime"].as()); - } - if (cmd_opts.count("cpu")) { - bench_opts.cpu(cmd_opts["cpu"].as()); - } - } catch (const cxxopts::OptionException& e) { - std::cout << "error parsing options: " << e.what() << std::endl; - exit(1); - } - benchpress::run_benchmarks(bench_opts); - float duration = std::chrono::duration_cast( - std::chrono::high_resolution_clock::now() - bp_start - ).count() / 1000.f; - std::cout << argv[0] << " " << duration << "s" << std::endl; - return 0; -} -#endif - -#endif // BENCHPRESS_HPP \ No newline at end of file diff --git a/benchmarks/thirdparty/cxxopts/LICENSE b/benchmarks/thirdparty/cxxopts/LICENSE deleted file mode 100644 index 324a2035..00000000 --- a/benchmarks/thirdparty/cxxopts/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2014 Jarryd Beck - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/benchmarks/thirdparty/cxxopts/cxxopts.hpp b/benchmarks/thirdparty/cxxopts/cxxopts.hpp deleted file mode 100644 index 94dd31ca..00000000 --- a/benchmarks/thirdparty/cxxopts/cxxopts.hpp +++ /dev/null @@ -1,1312 +0,0 @@ -/* - -Copyright (c) 2014 Jarryd Beck - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -*/ - -#ifndef CXX_OPTS_HPP -#define CXX_OPTS_HPP - -#if defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" -#endif - -#include -#include -#include -#include -#include -#include -#include -#include - -//when we ask cxxopts to use Unicode, help strings are processed using ICU, -//which results in the correct lengths being computed for strings when they -//are formatted for the help output -//it is necessary to make sure that can be found by the -//compiler, and that icu-uc is linked in to the binary. - -#ifdef CXXOPTS_USE_UNICODE -#include - -namespace cxxopts -{ - typedef icu::UnicodeString String; - - inline - String - toLocalString(std::string s) - { - return icu::UnicodeString::fromUTF8(s); - } - - class UnicodeStringIterator : public - std::iterator - { - public: - - UnicodeStringIterator(const icu::UnicodeString* s, int32_t pos) - : s(s) - , i(pos) - { - } - - value_type - operator*() const - { - return s->char32At(i); - } - - bool - operator==(const UnicodeStringIterator& rhs) const - { - return s == rhs.s && i == rhs.i; - } - - bool - operator!=(const UnicodeStringIterator& rhs) const - { - return !(*this == rhs); - } - - UnicodeStringIterator& - operator++() - { - ++i; - return *this; - } - - UnicodeStringIterator - operator+(int32_t v) - { - return UnicodeStringIterator(s, i + v); - } - - private: - const icu::UnicodeString* s; - int32_t i; - }; - - inline - String& - stringAppend(String&s, String a) - { - return s.append(std::move(a)); - } - - inline - String& - stringAppend(String& s, int n, UChar32 c) - { - for (int i = 0; i != n; ++i) - { - s.append(c); - } - - return s; - } - - template - String& - stringAppend(String& s, Iterator begin, Iterator end) - { - while (begin != end) - { - s.append(*begin); - ++begin; - } - - return s; - } - - inline - size_t - stringLength(const String& s) - { - return s.length(); - } - - inline - std::string - toUTF8String(const String& s) - { - std::string result; - s.toUTF8String(result); - - return result; - } -} - -namespace std -{ - cxxopts::UnicodeStringIterator - begin(const icu::UnicodeString& s) - { - return cxxopts::UnicodeStringIterator(&s, 0); - } - - cxxopts::UnicodeStringIterator - end(const icu::UnicodeString& s) - { - return cxxopts::UnicodeStringIterator(&s, s.length()); - } -} - -//ifdef CXXOPTS_USE_UNICODE -#else - -namespace cxxopts -{ - typedef std::string String; - - template - T - toLocalString(T&& t) - { - return t; - } - - inline - size_t - stringLength(const String& s) - { - return s.length(); - } - - inline - String& - stringAppend(String&s, String a) - { - return s.append(std::move(a)); - } - - inline - String& - stringAppend(String& s, size_t n, char c) - { - return s.append(n, c); - } - - template - String& - stringAppend(String& s, Iterator begin, Iterator end) - { - return s.append(begin, end); - } - - template - std::string - toUTF8String(T&& t) - { - return std::forward(t); - } - -} - -//ifdef CXXOPTS_USE_UNICODE -#endif - -namespace cxxopts -{ - class Value : public std::enable_shared_from_this - { - public: - - virtual void - parse(const std::string& text) const = 0; - - virtual void - parse() const = 0; - - virtual bool - has_arg() const = 0; - - virtual bool - has_default() const = 0; - - virtual bool - has_implicit() const = 0; - - virtual std::string - get_default_value() const = 0; - - virtual std::string - get_implicit_value() const = 0; - - virtual std::shared_ptr - default_value(const std::string& value) = 0; - - virtual std::shared_ptr - implicit_value(const std::string& value) = 0; - }; - - class OptionException : public std::exception - { - public: - OptionException(const std::string& message) - : m_message(message) - { - } - - virtual const char* - what() const noexcept - { - return m_message.c_str(); - } - - private: - std::string m_message; - }; - - class OptionSpecException : public OptionException - { - public: - - OptionSpecException(const std::string& message) - : OptionException(message) - { - } - }; - - class OptionParseException : public OptionException - { - public: - OptionParseException(const std::string& message) - : OptionException(message) - { - } - }; - - class option_exists_error : public OptionSpecException - { - public: - option_exists_error(const std::string& option) - : OptionSpecException(u8"Option ‘" + option + u8"’ already exists") - { - } - }; - - class invalid_option_format_error : public OptionSpecException - { - public: - invalid_option_format_error(const std::string& format) - : OptionSpecException(u8"Invalid option format ‘" + format + u8"’") - { - } - }; - - class option_not_exists_exception : public OptionParseException - { - public: - option_not_exists_exception(const std::string& option) - : OptionParseException(u8"Option ‘" + option + u8"’ does not exist") - { - } - }; - - class missing_argument_exception : public OptionParseException - { - public: - missing_argument_exception(const std::string& option) - : OptionParseException(u8"Option ‘" + option + u8"’ is missing an argument") - { - } - }; - - class option_requires_argument_exception : public OptionParseException - { - public: - option_requires_argument_exception(const std::string& option) - : OptionParseException(u8"Option ‘" + option + u8"’ requires an argument") - { - } - }; - - class option_not_has_argument_exception : public OptionParseException - { - public: - option_not_has_argument_exception - ( - const std::string& option, - const std::string& arg - ) - : OptionParseException( - u8"Option ‘" + option + u8"’ does not take an argument, but argument‘" - + arg + "’ given") - { - } - }; - - class option_not_present_exception : public OptionParseException - { - public: - option_not_present_exception(const std::string& option) - : OptionParseException(u8"Option ‘" + option + u8"’ not present") - { - } - }; - - class argument_incorrect_type : public OptionParseException - { - public: - argument_incorrect_type - ( - const std::string& arg - ) - : OptionParseException( - u8"Argument ‘" + arg + u8"’ failed to parse" - ) - { - } - }; - - namespace values - { - template - void - parse_value(const std::string& text, T& value) - { - std::istringstream is(text); - if (!(is >> value)) - { - std::cerr << "cannot parse empty value" << std::endl; - throw argument_incorrect_type(text); - } - - if (is.rdbuf()->in_avail() != 0) - { - throw argument_incorrect_type(text); - } - } - - template - void - parse_value(const std::string& text, std::vector& value) - { - T v; - parse_value(text, v); - value.push_back(v); - } - - inline - void - parse_value(const std::string& /*text*/, bool& value) - { - //TODO recognise on, off, yes, no, enable, disable - //so that we can write --long=yes explicitly - value = true; - } - - inline - void - parse_value(const std::string& text, std::string& value) - { - value = text; - } - - template - struct value_has_arg - { - static constexpr bool value = true; - }; - - template <> - struct value_has_arg - { - static constexpr bool value = false; - }; - - template - class standard_value : public Value - { - public: - standard_value() - : m_result(std::make_shared()) - , m_store(m_result.get()) - { - } - - standard_value(T* t) - : m_store(t) - { - } - - void - parse(const std::string& text) const - { - if (m_implicit && text.empty()) - { - parse_value(m_implicit_value, *m_store); - } - else - { - parse_value(text, *m_store); - } - } - - void - parse() const - { - parse_value(m_default_value, *m_store); - } - - bool - has_arg() const - { - return value_has_arg::value; - } - - bool - has_default() const - { - return m_default; - } - - bool - has_implicit() const - { - return m_implicit; - } - - virtual std::shared_ptr - default_value(const std::string& value){ - m_default = true; - m_default_value = value; - return shared_from_this(); - } - - virtual std::shared_ptr - implicit_value(const std::string& value){ - m_implicit = true; - m_implicit_value = value; - return shared_from_this(); - } - - std::string - get_default_value() const - { - return m_default_value; - } - - std::string - get_implicit_value() const - { - return m_implicit_value; - } - - const T& - get() const - { - if (m_store == nullptr) - { - return *m_result; - } - else - { - return *m_store; - } - } - - protected: - std::shared_ptr m_result; - T* m_store; - bool m_default = false; - std::string m_default_value; - bool m_implicit = false; - std::string m_implicit_value; - }; - } - - template - std::shared_ptr - value() - { - return std::make_shared>(); - } - - template - std::shared_ptr - value(T& t) - { - return std::make_shared>(&t); - } - - class OptionAdder; - - class OptionDetails - { - public: - OptionDetails - ( - const String& description, - std::shared_ptr value - ) - : m_desc(description) - , m_value(value) - , m_count(0) - { - } - - const String& - description() const - { - return m_desc; - } - - bool - has_arg() const - { - return m_value->has_arg(); - } - - void - parse(const std::string& text) - { - m_value->parse(text); - ++m_count; - } - - void - parse_default() - { - m_value->parse(); - ++m_count; - } - - int - count() const - { - return m_count; - } - - const Value& value() const { - return *m_value; - } - - template - const T& - as() const - { -#ifdef CXXOPTS_NO_RTTI - return static_cast&>(*m_value).get(); -#else - return dynamic_cast&>(*m_value).get(); -#endif - } - - private: - String m_desc; - std::shared_ptr m_value; - int m_count; - }; - - struct HelpOptionDetails - { - std::string s; - std::string l; - String desc; - bool has_arg; - bool has_default; - std::string default_value; - bool has_implicit; - std::string implicit_value; - std::string arg_help; - }; - - struct HelpGroupDetails - { - std::string name; - std::string description; - std::vector options; - }; - - class Options - { - public: - - Options(std::string program, std::string help_string = "") - : m_program(std::move(program)) - , m_help_string(toLocalString(std::move(help_string))) - { - } - - inline - void - parse(int& argc, char**& argv); - - inline - OptionAdder - add_options(std::string group = ""); - - inline - void - add_option - ( - const std::string& group, - const std::string& s, - const std::string& l, - std::string desc, - std::shared_ptr value, - std::string arg_help - ); - - int - count(const std::string& o) const - { - auto iter = m_options.find(o); - if (iter == m_options.end()) - { - return 0; - } - - return iter->second->count(); - } - - const OptionDetails& - operator[](const std::string& option) const - { - auto iter = m_options.find(option); - - if (iter == m_options.end()) - { - throw option_not_present_exception(option); - } - - return *iter->second; - } - - //parse positional arguments into the given option - inline - void - parse_positional(std::string option); - - inline - std::string - help(const std::vector& groups = {""}) const; - - inline - const std::vector - groups() const; - - inline - const HelpGroupDetails& - group_help(const std::string& group) const; - - private: - - inline - void - add_one_option - ( - const std::string& option, - std::shared_ptr details - ); - - inline - bool - consume_positional(std::string a); - - inline - void - add_to_option(const std::string& option, const std::string& arg); - - inline - void - parse_option - ( - std::shared_ptr value, - const std::string& name, - const std::string& arg = "" - ); - - inline - void - checked_parse_arg - ( - int argc, - char* argv[], - int& current, - std::shared_ptr value, - const std::string& name - ); - - inline - String - help_one_group(const std::string& group) const; - - std::string m_program; - String m_help_string; - - std::map> m_options; - std::string m_positional; - - //mapping from groups to help options - std::map m_help; - }; - - class OptionAdder - { - public: - - OptionAdder(Options& options, std::string group) - : m_options(options), m_group(std::move(group)) - { - } - - inline - OptionAdder& - operator() - ( - const std::string& opts, - const std::string& desc, - std::shared_ptr value - = ::cxxopts::value(), - std::string arg_help = "" - ); - - private: - Options& m_options; - std::string m_group; - }; - -} - -namespace cxxopts -{ - - namespace - { - - constexpr int OPTION_LONGEST = 30; - constexpr int OPTION_DESC_GAP = 2; - - std::basic_regex option_matcher - ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([a-zA-Z]+)"); - - std::basic_regex option_specifier - ("(([a-zA-Z]),)?([a-zA-Z0-9][-_a-zA-Z0-9]+)"); - - String - format_option - ( - const HelpOptionDetails& o - ) - { - auto& s = o.s; - auto& l = o.l; - - String result = " "; - - if (s.size() > 0) - { - result += "-" + toLocalString(s) + ","; - } - else - { - result += " "; - } - - if (l.size() > 0) - { - result += " --" + toLocalString(l); - } - - if (o.has_arg) - { - auto arg = o.arg_help.size() > 0 ? toLocalString(o.arg_help) : "arg"; - - if (o.has_implicit) - { - result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]"; - } - else - { - result += " " + arg; - } - } - - return result; - } - - String - format_description - ( - const HelpOptionDetails& o, - size_t start, - size_t width - ) - { - auto desc = o.desc; - - if (o.has_default) - { - desc += toLocalString(" (default:" + o.default_value + ")"); - } - - String result; - - auto current = std::begin(desc); - auto startLine = current; - auto lastSpace = current; - - auto size = size_t{}; - - while (current != std::end(desc)) - { - if (*current == ' ') - { - lastSpace = current; - } - - if (size > width) - { - if (lastSpace == startLine) - { - stringAppend(result, startLine, current + 1); - stringAppend(result, "\n"); - stringAppend(result, start, ' '); - startLine = current + 1; - lastSpace = startLine; - } - else - { - stringAppend(result, startLine, lastSpace); - stringAppend(result, "\n"); - stringAppend(result, start, ' '); - startLine = lastSpace + 1; - } - size = 0; - } - else - { - ++size; - } - - ++current; - } - - //append whatever is left - stringAppend(result, startLine, current); - - return result; - } - } - -OptionAdder -Options::add_options(std::string group) -{ - return OptionAdder(*this, std::move(group)); -} - -OptionAdder& -OptionAdder::operator() -( - const std::string& opts, - const std::string& desc, - std::shared_ptr value, - std::string arg_help -) -{ - std::match_results result; - std::regex_match(opts.c_str(), result, option_specifier); - - if (result.empty()) - { - throw invalid_option_format_error(opts); - } - - const auto& s = result[2]; - const auto& l = result[3]; - - m_options.add_option(m_group, s.str(), l.str(), desc, value, - std::move(arg_help)); - - return *this; -} - -void -Options::parse_option -( - std::shared_ptr value, - const std::string& /*name*/, - const std::string& arg -) -{ - value->parse(arg); -} - -void -Options::checked_parse_arg -( - int argc, - char* argv[], - int& current, - std::shared_ptr value, - const std::string& name -) -{ - if (current + 1 >= argc) - { - if (value->value().has_implicit()) - { - parse_option(value, name, ""); - } - else - { - throw missing_argument_exception(name); - } - } - else - { - if (argv[current + 1][0] == '-' && value->value().has_implicit()) - { - parse_option(value, name, ""); - } - else - { - parse_option(value, name, argv[current + 1]); - ++current; - } - } -} - -void -Options::add_to_option(const std::string& option, const std::string& arg) -{ - auto iter = m_options.find(option); - - if (iter == m_options.end()) - { - throw option_not_exists_exception(option); - } - - parse_option(iter->second, option, arg); -} - -bool -Options::consume_positional(std::string a) -{ - if (m_positional.size() > 0) - { - add_to_option(m_positional, a); - return true; - } - else - { - return false; - } -} - -void -Options::parse_positional(std::string option) -{ - m_positional = std::move(option); -} - -void -Options::parse(int& argc, char**& argv) -{ - int current = 1; - - int nextKeep = 1; - - while (current != argc) - { - std::match_results result; - std::regex_match(argv[current], result, option_matcher); - - if (result.empty()) - { - //not a flag - - //if true is returned here then it was consumed, otherwise it is - //ignored - if (consume_positional(argv[current])) - { - } - else - { - argv[nextKeep] = argv[current]; - ++nextKeep; - } - //if we return from here then it was parsed successfully, so continue - } - else - { - //short or long option? - if (result[4].length() != 0) - { - const std::string& s = result[4]; - - for (std::size_t i = 0; i != s.size(); ++i) - { - std::string name(1, s[i]); - auto iter = m_options.find(name); - - if (iter == m_options.end()) - { - throw option_not_exists_exception(name); - } - - auto value = iter->second; - - //if no argument then just add it - if (!value->has_arg()) - { - parse_option(value, name); - } - else - { - //it must be the last argument - if (i + 1 == s.size()) - { - checked_parse_arg(argc, argv, current, value, name); - } - else if (value->value().has_implicit()) - { - parse_option(value, name, ""); - } - else - { - //error - throw option_requires_argument_exception(name); - } - } - } - } - else if (result[1].length() != 0) - { - const std::string& name = result[1]; - - auto iter = m_options.find(name); - - if (iter == m_options.end()) - { - throw option_not_exists_exception(name); - } - - auto opt = iter->second; - - //equals provided for long option? - if (result[3].length() != 0) - { - //parse the option given - - //but if it doesn't take an argument, this is an error - if (!opt->has_arg()) - { - throw option_not_has_argument_exception(name, result[3]); - } - - parse_option(opt, name, result[3]); - } - else - { - if (opt->has_arg()) - { - //parse the next argument - checked_parse_arg(argc, argv, current, opt, name); - } - else - { - //parse with empty argument - parse_option(opt, name); - } - } - } - - } - - ++current; - } - - for (auto& opt : m_options) - { - auto& detail = opt.second; - auto& value = detail->value(); - - if(!detail->count() && value.has_default()){ - detail->parse_default(); - } - } - - argc = nextKeep; -} - -void -Options::add_option -( - const std::string& group, - const std::string& s, - const std::string& l, - std::string desc, - std::shared_ptr value, - std::string arg_help -) -{ - auto stringDesc = toLocalString(std::move(desc)); - auto option = std::make_shared(stringDesc, value); - - if (s.size() > 0) - { - add_one_option(s, option); - } - - if (l.size() > 0) - { - add_one_option(l, option); - } - - //add the help details - auto& options = m_help[group]; - - options.options.emplace_back(HelpOptionDetails{s, l, stringDesc, - value->has_arg(), - value->has_default(), value->get_default_value(), - value->has_implicit(), value->get_implicit_value(), - std::move(arg_help)}); -} - -void -Options::add_one_option -( - const std::string& option, - std::shared_ptr details -) -{ - auto in = m_options.emplace(option, details); - - if (!in.second) - { - throw option_exists_error(option); - } -} - -String -Options::help_one_group(const std::string& g) const -{ - typedef std::vector> OptionHelp; - - auto group = m_help.find(g); - if (group == m_help.end()) - { - return ""; - } - - OptionHelp format; - - size_t longest = 0; - - String result; - - if (!g.empty()) - { - result += toLocalString(" " + g + " options:\n\n"); - } - - for (const auto& o : group->second.options) - { - auto s = format_option(o); - longest = std::max(longest, stringLength(s)); - format.push_back(std::make_pair(s, String())); - } - - longest = std::min(longest, static_cast(OPTION_LONGEST)); - - //widest allowed description - auto allowed = size_t{76} - longest - OPTION_DESC_GAP; - - auto fiter = format.begin(); - for (const auto& o : group->second.options) - { - auto d = format_description(o, longest + OPTION_DESC_GAP, allowed); - - result += fiter->first; - if (stringLength(fiter->first) > longest) - { - result += "\n"; - result += toLocalString(std::string(longest + OPTION_DESC_GAP, ' ')); - } - else - { - result += toLocalString(std::string(longest + OPTION_DESC_GAP - - stringLength(fiter->first), - ' ')); - } - result += d; - result += "\n"; - - ++fiter; - } - - return result; -} - -std::string -Options::help(const std::vector& groups) const -{ - String result = "Usage:\n " + toLocalString(m_program) + " [OPTION...]" - + m_help_string + "\n\n"; - - for (std::size_t i = 0; i < groups.size(); ++i) - { - result += help_one_group(groups[i]); - if (i < groups.size() - 1) - { - result += "\n"; - } - } - - return toUTF8String(result); -} - -const std::vector -Options::groups() const -{ - std::vector g; - - std::transform( - m_help.begin(), - m_help.end(), - std::back_inserter(g), - [] (const std::map::value_type& pair) - { - return pair.first; - } - ); - - return g; -} - -const HelpGroupDetails& -Options::group_help(const std::string& group) const -{ - return m_help.at(group); -} - -} - -#if defined(__GNU__) -#pragma GCC diagnostic pop -#endif - -#endif //CXX_OPTS_HPP \ No newline at end of file diff --git a/develop/json.hpp b/develop/json.hpp index 77888236..b2bb49c8 100644 --- a/develop/json.hpp +++ b/develop/json.hpp @@ -7247,7 +7247,7 @@ class basic_json // wrapper to get a value for an operation const auto get_value = [&val](const std::string & op, const std::string & member, - bool string_type) -> basic_json& + bool string_type) -> basic_json & { // find value auto it = val.m_value.object->find(member); diff --git a/src/json.hpp b/src/json.hpp index 2abea0fd..dafc1082 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -14535,7 +14535,7 @@ class basic_json // wrapper to get a value for an operation const auto get_value = [&val](const std::string & op, const std::string & member, - bool string_type) -> basic_json& + bool string_type) -> basic_json & { // find value auto it = val.m_value.object->find(member); diff --git a/test/src/unit-inspection.cpp b/test/src/unit-inspection.cpp index 0043a9ec..21f637aa 100644 --- a/test/src/unit-inspection.cpp +++ b/test/src/unit-inspection.cpp @@ -316,8 +316,8 @@ TEST_CASE("object inspection") SECTION("round trips") { for (const auto& s : - {"3.141592653589793", "1000000000000000010E5" - }) + {"3.141592653589793", "1000000000000000010E5" + }) { json j1 = json::parse(s); std::string s1 = j1.dump();