----------------------------------------------------------- This is an automatically generated e-mail. To reply, visit: https://reviews.apache.org/r/52877/ -----------------------------------------------------------
(Updated Jan. 5, 2017, 6:05 p.m.) Review request for mesos, Adam B, Benjamin Bannier, Joris Van Remoortere, and Michael Park. Bugs: MESOS-6349 https://issues.apache.org/jira/browse/MESOS-6349 Repository: mesos Description (updated) ------- Resolves an isse with the JSON serialization functions where floats would use the process active locale as a decimal separator instead of the JSON conformant period (`.`). Diffs ----- 3rdparty/stout/include/stout/json.hpp 62ce15274677112d142a3c829b4a9f06258c9e2c 3rdparty/stout/include/stout/jsonify.hpp 8220d001001e8b9f247c05be58534f1174611aad Diff: https://reviews.apache.org/r/52877/diff/ Testing ------- In order to test the different options the following benchmarking program was written using [Google benchmark](https://github.com/google/benchmark): ```c++ #include <benchmark/benchmark_api.h> #include <stout/json.hpp> #include <iostream> #include <random> #include <string> #include <sstream> static void BM_Jsonify(benchmark::State& state) { while (state.KeepRunning()) { state.PauseTiming(); // Randomly generated set of numbers. std::vector<double> doubles = { 54366462691.1798, 3.35465250645312, 3056184950.05953, 74057564.7741182, 280.445791893924, 3446.63176368415, 419012114.325581, 3464212.51004162, 1.45156507568354, 13304750.4216248, 7716457488648.00, 700533630.650588, 679.659801950981, 307059152.688268, 5615744.28063731, 859.902033900705, 293878.810967451, 284685668.155903, 722683811.462448, 407.682284299325, 9874834.88341080, 4829649.14505646, 3045544.72401146, 1112707.08627010, 8379539.79388719, 3004161.89676627, 3866662.79849617, 3871151.73991937, 4090119.26657417, 4118699.88616345, 2104416.18322520, 9896898.63226234, 5957851.08773457, 3501068.52003430, 7524714.36218293, 4333874.01982647, 9562008.06930384, 3374957.45494027, 5867075.07463260, 815499.697450741, 600936.470830026, 9661425.72632153, 6392256.71537575, 7517969.33139398, 9031912.03425553, 5497593.85752735, 815419.808032205, 5098659.46057626, 930160.667551563, 5970944.98217500, 6166534.92677787, 3541537.67676275, 1291933.13549156, 9185094.72404290, 4507338.03523123, 9559754.89147696, 6152898.39204769, 2358966.41795446, 6520510.92218183, 2201757.78606032, 4960487.80737309, 1784969.91832029, 3858390.23735070, 1439952.27402359, 6407199.91163080, 6130379.95590661, 6427890.23913244, 2128879.29010338, 8175291.42483598, 1587278.27639235, 7493343.68705307, 4853439.25371342, 2564845.15639735, 1415661.63929173, 6388168.79342734, 3003424.90116775, 6915390.10600792, 7115390.65502377, 5288515.90088063, 1209208.86882085, 4483111.91884606, 5974377.97163572, 5821054.89489766, 8284136.84073623, 1607044.34898051, 3255087.31267763, 2305369.43079747, 1282392.57487249, 4884797.49134467, 5119420.62129117, 6276725.07755672, 3054999.92210194, 3594116.58894982, 6691568.49651968, 265562.721872220, 2864878.07276221, 627299.902077148, 5885179.44212665, 7654144.98508934, 590857.599685431 }; state.ResumeTiming(); benchmark::DoNotOptimize(jsonify(object)); } } BENCHMARK(BM_Jsonify); BENCHMARK_MAIN(); ``` The program creates a JSON object with 50 floats in it, and then it creates its string representation. Since the algorithms in `json.hpp` and `jsonify.hpp` are similar but not the same, they were benchmarked as different. While the original solutions are non solutions since they produce erroneous results if localization is active, they constitute a baseline for comparison with the alternatives. These are the benchmarks for the original algorithms and their alternatives: 1. `json.hpp` default: ```c++ char buffer[50] {}; snprintf( buffer, sizeof(buffer), "%#.*g", std::numeric_limits<double>::digits10, number.value); std::string trimmed = strings::trim(buffer, strings::SUFFIX, "0"); *stream_ << trimmed << (trimmed.back() == '.' ? "0" : ""); ``` Perfomance ``` Run on (8 X 2800 MHz CPU s) 2017-01-03 15:06:35 Benchmark Time CPU Iterations ------------------------------------------------- BM_Jsonify 985 ns 986 ns 714227 ``` 2. `jsonify.hpp` default (it changes the call to `strings::trim` in order to avoid string copies: ```c++ char buffer[50] {}; const int size = snprintf( buffer, sizeof(buffer), "%#.*g", std::numeric_limits<double>::digits10, number.value); int back = size - 1; for (; back > 0; --back) { if (buffer[back] != '0') { break; } buffer[back] = '\0'; } *stream_ << buffer << (buffer[back] == '.' ? "0" : ""); ``` Performance ``` Run on (8 X 2800 MHz CPU s) 2017-01-03 15:05:34 Benchmark Time CPU Iterations ------------------------------------------------- BM_Jsonify 968 ns 969 ns 723417 ``` 3. Proposal 1, manually searching for a character which is not expected (and it is assumed to be the decimal separator) and replacing it: ```c++ char buffer[50] {}; int size = snprintf( buffer, sizeof(buffer), "%#.*g", std::numeric_limits<double>::digits10, double_); const char separator = std::use_facet<std::numpunct<char>>(*stream_.getloc()).decimal_point(); for (int i = 0; i < size; ++i) { if (buffer[i] == separator) { buffer[i] = '.'; break; } } int back = size - 1; for (; back > 0; --back) { if (buffer[back] != '0') { break; } buffer[back] = '\0'; } *stream_ << buffer << (buffer[back] == '.' ? "0" : ""); ``` Performance ``` Run on (8 X 2800 MHz CPU s) 2017-01-03 15:07:39 Benchmark Time CPU Iterations ------------------------------------------------- BM_Jsonify 982 ns 981 ns 704544 ``` 4. Proposal 2, the C++ approach, use streams and stream manipulators: ```c++ std::stringstream ss; ss.imbue(std::locale::classic()); ss << std::setprecision(std::numeric_limits<double>::digits10) << std::showpoint << number.value; std::string trimmed = strings::trim(ss.str(), strings::SUFFIX, "0"); return out << trimmed << (trimmed.back() == '.' ? "0" : ""); ``` Performance ``` Run on (8 X 2800 MHz CPU s) 2017-01-03 15:09:40 Benchmark Time CPU Iterations ------------------------------------------------- BM_Jsonify 977 ns 978 ns 715571 ``` 5. Proposal 3, try to avoid copies (there are still some being created): ```c++ std::stringstream ss; ss.imbue(std::locale::classic()); ss << std::setprecision(std::numeric_limits<double>::digits10) << std::showpoint << number.value; std::string representation = ss.str(); int back = representation.size() - 1; for (; back > 0; --back) { if (representation[back] != '0') { break; } } representation.resize(back + 1); *stream_ << representation << (representation[back] == '.' ? "0" : ""); ``` Performance ``` Run on (8 X 2800 MHz CPU s) 2017-01-03 15:10:52 Benchmark Time CPU Iterations ------------------------------------------------- BM_Jsonify 982 ns 984 ns 714935 ``` 6. Proposal 4: Using thread local variables: ```c++ static THREAD_LOCAL std::stringstream* ss = nullptr; static THREAD_LOCAL bool initialized = false; if (!initialized) { ss = new std::stringstream; ss->imbue(std::locale::classic()); *ss << std::setprecission(std::numeric_limits<double>::digits10) << std::showpoint; initialized = true; } *ss << double_; std::string trimmed = strings::trim(ss->str(), strings::SUFFIX, "0"); *stream_ << trimmed << (trimmed.back() == '.' ? "0" : ""); ss->str(""); ``` Performance ``` Run on (8 X 2800 MHz CPU s) 2017-01-03 15:12:37 Benchmark Time CPU Iterations ------------------------------------------------- BM_Jsonify 980 ns 980 ns 716413 ``` Surprisingly, proposal 2 seemed to be the most efficient after multiple runs of the suite. Thanks, Alexander Rojas