Don't benchmark unless haswell is available

This commit is contained in:
John Keiser 2020-08-10 14:03:50 -07:00
parent 638f1deb62
commit 18564f1ae2
2 changed files with 349 additions and 125 deletions

View File

@ -2,6 +2,10 @@
#define SIMDJSON_IMPLEMENTATION_WESTMERE 0 #define SIMDJSON_IMPLEMENTATION_WESTMERE 0
#define SIMDJSON_IMPLEMENTATION_AMD64 0 #define SIMDJSON_IMPLEMENTATION_AMD64 0
#include <iostream>
#include <sstream>
#include <random>
#include "simdjson.h" #include "simdjson.h"
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
@ -9,7 +13,8 @@ SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
SIMDJSON_POP_DISABLE_WARNINGS SIMDJSON_POP_DISABLE_WARNINGS
#include "simdjson.cpp" #include "simdjson.cpp"
#include "twitter/sax_tweet_reader.h"
#if SIMDJSON_EXCEPTIONS
using namespace benchmark; using namespace benchmark;
using namespace simdjson; using namespace simdjson;
@ -19,6 +24,10 @@ using std::endl;
const char *TWITTER_JSON = SIMDJSON_BENCHMARK_DATA_DIR "twitter.json"; const char *TWITTER_JSON = SIMDJSON_BENCHMARK_DATA_DIR "twitter.json";
const int REPETITIONS = 10; const int REPETITIONS = 10;
#if SIMDJSON_IMPLEMENTATION_HASWELL
#include "twitter/sax_tweet_reader.h"
static void sax_tweets(State &state) { static void sax_tweets(State &state) {
// Load twitter.json to a buffer // Load twitter.json to a buffer
padded_string json; padded_string json;
@ -50,7 +59,9 @@ BENCHMARK(sax_tweets)->Repetitions(REPETITIONS)->ComputeStatistics("max", [](con
return *(std::max_element(std::begin(v), std::end(v))); return *(std::max_element(std::begin(v), std::end(v)));
})->DisplayAggregatesOnly(true); })->DisplayAggregatesOnly(true);
#if SIMDJSON_EXCEPTIONS #endif // SIMDJSON_IMPLEMENTATION_HASWELL
#include "twitter/tweet.h"
simdjson_really_inline uint64_t nullable_int(dom::element element) { simdjson_really_inline uint64_t nullable_int(dom::element element) {
if (element.is_null()) { return 0; } if (element.is_null()) { return 0; }
@ -106,8 +117,6 @@ BENCHMARK(dom_tweets)->Repetitions(REPETITIONS)->ComputeStatistics("max", [](con
return *(std::max_element(std::begin(v), std::end(v))); return *(std::max_element(std::begin(v), std::end(v)));
})->DisplayAggregatesOnly(true); })->DisplayAggregatesOnly(true);
#endif // SIMDJSON_EXCEPTIONS
static void dom_parse(State &state) { static void dom_parse(State &state) {
// Load twitter.json to a buffer // Load twitter.json to a buffer
padded_string json; padded_string json;
@ -133,4 +142,218 @@ BENCHMARK(dom_parse)->Repetitions(REPETITIONS)->ComputeStatistics("max", [](cons
return *(std::max_element(std::begin(v), std::end(v))); return *(std::max_element(std::begin(v), std::end(v)));
})->DisplayAggregatesOnly(true); })->DisplayAggregatesOnly(true);
/********************
* Large file parsing benchmarks:
********************/
static std::string build_json_array(size_t N) {
std::default_random_engine e;
std::uniform_real_distribution<> dis(0, 1);
std::stringstream myss;
myss << "[" << std::endl;
if(N > 0) {
myss << "{ \"x\":" << dis(e) << ", \"y\":" << dis(e) << ", \"z\":" << dis(e) << "}" << std::endl;
}
for(size_t i = 1; i < N; i++) {
myss << "," << std::endl;
myss << "{ \"x\":" << dis(e) << ", \"y\":" << dis(e) << ", \"z\":" << dis(e) << "}";
}
myss << std::endl;
myss << "]" << std::endl;
std::string answer = myss.str();
std::cout << "Creating a source file spanning " << (answer.size() + 512) / 1024 << " KB " << std::endl;
return answer;
}
static const simdjson::padded_string& get_my_json_str() {
static simdjson::padded_string s = build_json_array(1000000);
return s;
}
struct my_point {
double x;
double y;
double z;
};
// ./benchmark/bench_sax --benchmark_filter=largerandom
/***
* We start with the naive DOM-based approach.
**/
static void dom_parse_largerandom(State &state) {
// Load twitter.json to a buffer
const padded_string& json = get_my_json_str();
// Allocate
dom::parser parser;
if (auto error = parser.allocate(json.size())) { cerr << error << endl; return; };
// Read
size_t bytes = 0;
simdjson::error_code error;
for (SIMDJSON_UNUSED auto _ : state) {
std::vector<my_point> container;
dom::element doc;
if ((error = parser.parse(json).get(doc))) {
std::cerr << "failure: " << error << std::endl;
throw "Parsing failed";
};
for (auto p : doc) {
container.emplace_back(my_point{p["x"], p["y"], p["z"]});
}
bytes += json.size();
benchmark::DoNotOptimize(container.data());
}
// Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
state.counters["Gigabytes"] = benchmark::Counter(
double(bytes), benchmark::Counter::kIsRate,
benchmark::Counter::OneK::kIs1000); // For GiB : kIs1024
state.counters["docs"] = Counter(double(state.iterations()), benchmark::Counter::kIsRate);
}
BENCHMARK(dom_parse_largerandom)->Repetitions(REPETITIONS)->ComputeStatistics("max", [](const std::vector<double>& v) -> double {
return *(std::max_element(std::begin(v), std::end(v)));
})->DisplayAggregatesOnly(true);
#if SIMDJSON_IMPLEMENTATION_HASWELL
/***
* Next we are going to code the SAX approach.
**/
SIMDJSON_TARGET_HASWELL
namespace largerandom {
namespace {
using namespace simdjson;
using namespace haswell;
using namespace haswell::stage2;
struct sax_point_reader_visitor {
public:
sax_point_reader_visitor(std::vector<my_point> &_points) : points(_points) {
}
simdjson_really_inline error_code visit_document_start(json_iterator &) { return SUCCESS; }
simdjson_really_inline error_code visit_object_start(json_iterator &) { return SUCCESS; }
simdjson_really_inline error_code visit_key(json_iterator &, const uint8_t *key) {
switch(key[0]) {
case 'x':
idx = 0;
break;
case 'y':
idx = 2;
break;
case 'z':
idx = 3;
break;
}
return SUCCESS;
}
simdjson_really_inline error_code visit_primitive(json_iterator &, const uint8_t *value) {
return numberparsing::parse_double(value).get(buffer[idx]);
}
simdjson_really_inline error_code visit_array_start(json_iterator &) { return SUCCESS; }
simdjson_really_inline error_code visit_array_end(json_iterator &) { return SUCCESS; }
simdjson_really_inline error_code visit_object_end(json_iterator &) { return SUCCESS; }
simdjson_really_inline error_code visit_document_end(json_iterator &) { return SUCCESS; }
simdjson_really_inline error_code visit_empty_array(json_iterator &) { return SUCCESS; }
simdjson_really_inline error_code visit_empty_object(json_iterator &) { return SUCCESS; }
simdjson_really_inline error_code visit_root_primitive(json_iterator &, const uint8_t *) { return SUCCESS; }
simdjson_really_inline error_code increment_count(json_iterator &) { return SUCCESS; }
std::vector<my_point> &points;
size_t idx{0};
double buffer[3];
};
struct sax_point_reader {
std::vector<my_point> points;
std::unique_ptr<uint8_t[]> string_buf;
size_t capacity;
dom_parser_implementation dom_parser;
sax_point_reader();
error_code set_capacity(size_t new_capacity);
error_code read_points(const padded_string &json);
}; // struct sax_point_reader
sax_point_reader::sax_point_reader() : points{}, string_buf{}, capacity{0}, dom_parser() {
}
error_code sax_point_reader::set_capacity(size_t new_capacity) {
// string_capacity copied from document::allocate
size_t string_capacity = SIMDJSON_ROUNDUP_N(5 * new_capacity / 3 + 32, 64);
string_buf.reset(new (std::nothrow) uint8_t[string_capacity]);
if (auto error = dom_parser.set_capacity(new_capacity)) { return error; }
if (capacity == 0) { // set max depth the first time only
if (auto error = dom_parser.set_max_depth(DEFAULT_MAX_DEPTH)) { return error; }
}
capacity = new_capacity;
return SUCCESS;
}
error_code sax_point_reader::read_points(const padded_string &json) {
// Allocate capacity if needed
points.clear();
if (capacity < json.size()) {
if (auto error = set_capacity(capacity)) { return error; }
}
// Run stage 1 first.
if (auto error = dom_parser.stage1((uint8_t *)json.data(), json.size(), false)) { return error; }
// Then walk the document, parsing the tweets as we go
json_iterator iter(dom_parser, 0);
sax_point_reader_visitor visitor(points);
if (auto error = iter.walk_document<false>(visitor)) { return error; }
return SUCCESS;
}
} // unnamed namespace
} // namespace largerandom
SIMDJSON_UNTARGET_REGION
// ./benchmark/bench_sax --benchmark_filter=largerandom
static void sax_parse_largerandom(State &state) {
// Load twitter.json to a buffer
const padded_string& json = get_my_json_str();
// Allocate
largerandom::sax_point_reader reader;
if (auto error = reader.set_capacity(json.size())) { throw error; }
// warming
for(size_t i = 0; i < 10; i++) {
if (auto error = reader.read_points(json)) { throw error; }
}
// Read
size_t bytes = 0;
for (SIMDJSON_UNUSED auto _ : state) {
if (auto error = reader.read_points(json)) { throw error; }
bytes += json.size();
benchmark::DoNotOptimize(reader.points.data());
}
// Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
state.counters["Gigabytes"] = benchmark::Counter(
double(bytes), benchmark::Counter::kIsRate,
benchmark::Counter::OneK::kIs1000); // For GiB : kIs1024
state.counters["docs"] = Counter(double(state.iterations()), benchmark::Counter::kIsRate);
}
BENCHMARK(sax_parse_largerandom)->Repetitions(REPETITIONS)->ComputeStatistics("max", [](const std::vector<double>& v) -> double {
return *(std::max_element(std::begin(v), std::end(v)));
})->DisplayAggregatesOnly(true);
#endif // SIMDJSON_IMPLEMENTATION_HASWELL
#endif // SIMDJSON_EXCEPTIONS
BENCHMARK_MAIN(); BENCHMARK_MAIN();

View File

@ -316,7 +316,7 @@ simdjson_really_inline bool parse_exponent(SIMDJSON_UNUSED const uint8_t *const
// a single simdjson_unlikely path would be faster. The reasoning is sound, but the compiler may // a single simdjson_unlikely path would be faster. The reasoning is sound, but the compiler may
// not oblige and may, in fact, generate two distinct paths in any case. It might be // not oblige and may, in fact, generate two distinct paths in any case. It might be
// possible to do uint64_t(p - start_exp - 1) >= 18 but it could end up trading off // possible to do uint64_t(p - start_exp - 1) >= 18 but it could end up trading off
// instructions for a likely branch, an unconclusive gain. // instructions for a simdjson_likely branch, an unconclusive gain.
// If there were no digits, it's an error. // If there were no digits, it's an error.
if (simdjson_unlikely(p == start_exp)) { if (simdjson_unlikely(p == start_exp)) {
@ -501,6 +501,8 @@ simdjson_really_inline bool parse_number(const uint8_t *const src, W &writer) {
return is_structural_or_whitespace(*p); return is_structural_or_whitespace(*p);
} }
// SAX functions
namespace {
// Parse any number from 0 to 18,446,744,073,709,551,615 // Parse any number from 0 to 18,446,744,073,709,551,615
SIMDJSON_UNUSED simdjson_really_inline simdjson_result<uint64_t> parse_unsigned(const uint8_t * const src) noexcept { SIMDJSON_UNUSED simdjson_really_inline simdjson_result<uint64_t> parse_unsigned(const uint8_t * const src) noexcept {
const uint8_t *p = src; const uint8_t *p = src;
@ -542,59 +544,58 @@ SIMDJSON_UNUSED simdjson_really_inline simdjson_result<uint64_t> parse_unsigned(
// Parse any number from 0 to 18,446,744,073,709,551,615 // Parse any number from 0 to 18,446,744,073,709,551,615
// Call this version of the method if you regularly expect 8- or 16-digit numbers. // Call this version of the method if you regularly expect 8- or 16-digit numbers.
// simdjson_really_inline simdjson_result<uint64_t> parse_large_unsigned(const uint8_t * const src) noexcept { SIMDJSON_UNUSED simdjson_really_inline simdjson_result<uint64_t> parse_large_unsigned(const uint8_t * const src) noexcept {
// const uint8_t *p = src; const uint8_t *p = src;
// // //
// // Parse the integer part. // Parse the integer part.
// // //
// const uint8_t *const start_digits = p; uint64_t i = 0;
// uint64_t i = 0; if (is_made_of_eight_digits_fast(p)) {
// if (is_made_of_eight_digits_fast(p)) { i = i * 100000000 + parse_eight_digits_unrolled(p);
// i = i * 100000000 + parse_eight_digits_unrolled(p); p += 8;
// p += 8; if (is_made_of_eight_digits_fast(p)) {
// if (is_made_of_eight_digits_fast(p)) { i = i * 100000000 + parse_eight_digits_unrolled(p);
// i = i * 100000000 + parse_eight_digits_unrolled(p); p += 8;
// p += 8; if (parse_digit(*p, i)) { // digit 17
// if (parse_digit(*p, i)) { // digit 17 p++;
// p++; if (parse_digit(*p, i)) { // digit 18
// if (parse_digit(*p, i)) { // digit 18 p++;
// p++; if (parse_digit(*p, i)) { // digit 19
// if (parse_digit(*p, i)) { // digit 19 p++;
// p++; if (parse_digit(*p, i)) { // digit 20
// if (parse_digit(*p, i)) { // digit 20 p++;
// p++; if (parse_digit(*p, i)) { return NUMBER_ERROR; } // 21 digits is an error
// if (parse_digit(*p, i)) { return NUMBER_ERROR; } // 21 digits is an error // Positive overflow check:
// // Positive overflow check: // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the
// // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the // biggest uint64_t.
// // biggest uint64_t. // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX.
// // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. // If we got here, it's a 20 digit number starting with the digit "1".
// // If we got here, it's a 20 digit number starting with the digit "1". // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller
// // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller // than 1,553,255,926,290,448,384.
// // than 1,553,255,926,290,448,384. // - That is smaller than the smallest possible 20-digit number the user could write:
// // - That is smaller than the smallest possible 20-digit number the user could write: // 10,000,000,000,000,000,000.
// // 10,000,000,000,000,000,000. // - Therefore, if the number is positive and lower than that, it's overflow.
// // - Therefore, if the number is positive and lower than that, it's overflow. // - The value we are looking at is less than or equal to 9,223,372,036,854,775,808 (INT64_MAX).
// // - The value we are looking at is less than or equal to 9,223,372,036,854,775,808 (INT64_MAX). //
// // if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return NUMBER_ERROR; }
// if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return NUMBER_ERROR; } }
// } }
// } }
// } }
// } } // 16 digits
// } // 16 digits } else { // 8 digits
// } else { // 8 digits // Less than 8 digits can't overflow, simpler logic here.
// // Less than 8 digits can't overflow, simpler logic here. if (parse_digit(*p, i)) { p++; } else { return NUMBER_ERROR; }
// if (parse_digit(*p, i)) { p++; } else { return NUMBER_ERROR; } while (parse_digit(*p, i)) { p++; }
// while (parse_digit(*p, i)) { p++; } }
// }
// if (!is_structural_or_whitespace(*p, i)) { return NUMBER_ERROR; } if (!is_structural_or_whitespace(*p)) { return NUMBER_ERROR; }
// // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error.
// int digit_count = int(p - src); int digit_count = int(p - src);
// if (digit_count == 0 || ('0' == *src && digit_count > 1)) { return NUMBER_ERROR; } if (digit_count == 0 || ('0' == *src && digit_count > 1)) { return NUMBER_ERROR; }
// return i; return i;
// } }
// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 // Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
SIMDJSON_UNUSED simdjson_really_inline simdjson_result<int64_t> parse_integer(const uint8_t *src) noexcept { SIMDJSON_UNUSED simdjson_really_inline simdjson_result<int64_t> parse_integer(const uint8_t *src) noexcept {
@ -646,82 +647,82 @@ SIMDJSON_UNUSED simdjson_really_inline simdjson_result<int64_t> parse_integer(co
return negative ? (~i+1) : i; return negative ? (~i+1) : i;
} }
// simdjson_really_inline simdjson_result<double> parse_double(const uint8_t * src) noexcept { SIMDJSON_UNUSED simdjson_really_inline simdjson_result<double> parse_double(const uint8_t * src) noexcept {
// // //
// // Check for minus sign // Check for minus sign
// // //
// bool negative = (*src == '-'); bool negative = (*src == '-');
// src += negative; src += negative;
// // //
// // Parse the integer part. // Parse the integer part.
// // //
// uint64_t i = 0; uint64_t i = 0;
// const uint8_t *p = src; const uint8_t *p = src;
// p += parse_digit(*p, i); p += parse_digit(*p, i);
// bool leading_zero = (i == 0); bool leading_zero = (i == 0);
// while (parse_digit(*p, i)) { p++; } while (parse_digit(*p, i)) { p++; }
// // no integer digits, or 0123 (zero must be solo) // no integer digits, or 0123 (zero must be solo)
// if ( p == src || (leading_zero && p != src+1)) { return NUMBER_ERROR; } if ( p == src || (leading_zero && p != src+1)) { return NUMBER_ERROR; }
// // //
// // Parse the decimal part. // Parse the decimal part.
// // //
// int64_t exponent = 0; int64_t exponent = 0;
// bool overflow; bool overflow;
// if (likely(*p == '.')) { if (simdjson_likely(*p == '.')) {
// p++; p++;
// const uint8_t *start_decimal_digits = p; const uint8_t *start_decimal_digits = p;
// if (!parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits if (!parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits
// p++; p++;
// while (parse_digit(*p, i)) { p++; } while (parse_digit(*p, i)) { p++; }
// exponent = -(p - start_decimal_digits); exponent = -(p - start_decimal_digits);
// // Overflow check. 19 digits (minus the decimal) may be overflow. // Overflow check. 19 digits (minus the decimal) may be overflow.
// overflow = p-src-1 >= 19; overflow = p-src-1 >= 19;
// if (SIMDJSON_unlikely(overflow && leading_zero)) { if (simdjson_unlikely(overflow && leading_zero)) {
// // Skip leading 0.00000 and see if it still overflows // Skip leading 0.00000 and see if it still overflows
// const uint8_t *start_digits = src + 2; const uint8_t *start_digits = src + 2;
// while (*start_digits == '0') { start_digits++; } while (*start_digits == '0') { start_digits++; }
// overflow = start_digits-src >= 19; overflow = start_digits-src >= 19;
// } }
// } else { } else {
// overflow = p-src >= 19; overflow = p-src >= 19;
// } }
// // //
// // Parse the exponent // Parse the exponent
// // //
// if (*p == 'e' || *p == 'E') { if (*p == 'e' || *p == 'E') {
// p++; p++;
// bool exp_neg = *p == '-'; bool exp_neg = *p == '-';
// p += exp_neg || *p == '+'; p += exp_neg || *p == '+';
// uint64_t exp = 0; uint64_t exp = 0;
// const uint8_t *start_exp_digits = p; const uint8_t *start_exp_digits = p;
// while (parse_digit(*p, exp)) { p++; } while (parse_digit(*p, exp)) { p++; }
// // no exp digits, or 20+ exp digits // no exp digits, or 20+ exp digits
// if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; }
// exponent += exp_neg ? 0-exp : exp; exponent += exp_neg ? 0-exp : exp;
// overflow = overflow || exponent < FASTFLOAT_SMALLEST_POWER || exponent > FASTFLOAT_LARGEST_POWER; overflow = overflow || exponent < FASTFLOAT_SMALLEST_POWER || exponent > FASTFLOAT_LARGEST_POWER;
// } }
// //
// // Assemble (or slow-parse) the float
// //
// if (likely(!overflow)) {
// bool success = false;
// double d = compute_float_64(exponent, i, negative, &success);
// if (success) { return d; }
// }
// double d;
// if (!parse_float_strtod(src-negative, &d)) {
// return NUMBER_ERROR;
// }
// return d;
// }
//
// Assemble (or slow-parse) the float
//
if (simdjson_likely(!overflow)) {
bool success = true;
double d = compute_float_64(exponent, i, negative, &success);
if (success) { return d; }
}
double d;
if (!parse_float_strtod(src-negative, &d)) {
return NUMBER_ERROR;
}
return d;
}
} //namespace {}
#endif // SIMDJSON_SKIPNUMBERPARSING #endif // SIMDJSON_SKIPNUMBERPARSING
} // namespace numberparsing } // namespace numberparsing