diff --git a/benchmark/bench_dom_api.cpp b/benchmark/bench_dom_api.cpp index ea6316de2..f9494fc51 100644 --- a/benchmark/bench_dom_api.cpp +++ b/benchmark/bench_dom_api.cpp @@ -256,7 +256,6 @@ static void print_json(State& state) noexcept { // Prints the number of results in twitter.json padded_string json = get_corpus(JSON_TEST_PATH); dom::parser parser; - if (!parser.allocate_capacity(json.length())) { cerr << "allocation failed" << endl; return; } if (int error = json_parse(json, parser); error != SUCCESS) { cerr << error_message(error) << endl; return; } for (auto _ : state) { std::stringstream s; diff --git a/benchmark/bench_parse_call.cpp b/benchmark/bench_parse_call.cpp index a1719ff89..70f731609 100644 --- a/benchmark/bench_parse_call.cpp +++ b/benchmark/bench_parse_call.cpp @@ -9,10 +9,10 @@ const padded_string EMPTY_ARRAY("[]", 2); SIMDJSON_PUSH_DISABLE_WARNINGS SIMDJSON_DISABLE_DEPRECATED_WARNING static void json_parse(State& state) { - dom::parser parser; - if (parser.set_capacity(EMPTY_ARRAY.length())) { return; } + ParsedJson pj; + if (!pj.allocate_capacity(EMPTY_ARRAY.length())) { return; } for (auto _ : state) { - auto error = simdjson::json_parse(EMPTY_ARRAY, parser); + auto error = json_parse(EMPTY_ARRAY, pj); if (error) { return; } } } @@ -20,7 +20,7 @@ SIMDJSON_POP_DISABLE_WARNINGS BENCHMARK(json_parse); static void parser_parse_error_code(State& state) { dom::parser parser; - if (parser.set_capacity(EMPTY_ARRAY.length())) { return; } + if (parser.allocate(EMPTY_ARRAY.length())) { return; } for (auto _ : state) { auto [doc, error] = parser.parse(EMPTY_ARRAY); if (error) { return; } @@ -29,7 +29,7 @@ static void parser_parse_error_code(State& state) { BENCHMARK(parser_parse_error_code); static void parser_parse_exception(State& state) { dom::parser parser; - if (parser.set_capacity(EMPTY_ARRAY.length())) { return; } + if (parser.allocate(EMPTY_ARRAY.length())) { return; } for (auto _ : state) { try { UNUSED dom::element doc = parser.parse(EMPTY_ARRAY); diff --git a/benchmark/benchmarker.h b/benchmark/benchmarker.h index 4ea43bdd6..68aa9edc1 100644 --- a/benchmark/benchmarker.h +++ b/benchmark/benchmarker.h @@ -294,7 +294,10 @@ struct benchmarker { // Allocate dom::parser collector.start(); dom::parser parser; - error_code alloc_error = parser.set_capacity(json.size()); + error_code error = parser.allocate(json.size()); + if (error) { + exit_error(string("Unable to allocate_stage ") + to_string(json.size()) + " bytes for the JSON result: " + error_message(error)); + } event_count allocate_count = collector.end(); allocate_stage << allocate_count; // Run it once to get hot buffers @@ -305,14 +308,11 @@ struct benchmarker { } } - if (alloc_error) { - exit_error(string("Unable to allocate_stage ") + to_string(json.size()) + " bytes for the JSON result: " + error_message(alloc_error)); - } verbose() << "[verbose] allocated memory for parsed JSON " << endl; // Stage 1 (find structurals) collector.start(); - error_code error = active_implementation->stage1((const uint8_t *)json.data(), json.size(), parser, false); + error = active_implementation->stage1((const uint8_t *)json.data(), json.size(), parser, false); event_count stage1_count = collector.end(); stage1 << stage1_count; if (error) { diff --git a/benchmark/linux/linux-perf-events.h b/benchmark/linux/linux-perf-events.h index 603f1f6c6..a6ecb7a40 100644 --- a/benchmark/linux/linux-perf-events.h +++ b/benchmark/linux/linux-perf-events.h @@ -56,25 +56,29 @@ public: temp_result_vec.resize(num_events * 2 + 1); } - ~LinuxEvents() { close(fd); } + ~LinuxEvents() { if (fd != -1) { close(fd); } } inline void start() { - if (ioctl(fd, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP) == -1) { - report_error("ioctl(PERF_EVENT_IOC_RESET)"); - } + if (fd != -1) { + if (ioctl(fd, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP) == -1) { + report_error("ioctl(PERF_EVENT_IOC_RESET)"); + } - if (ioctl(fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) == -1) { - report_error("ioctl(PERF_EVENT_IOC_ENABLE)"); + if (ioctl(fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) == -1) { + report_error("ioctl(PERF_EVENT_IOC_ENABLE)"); + } } } inline void end(std::vector &results) { - if (ioctl(fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP) == -1) { - report_error("ioctl(PERF_EVENT_IOC_DISABLE)"); - } + if (fd != -1) { + if (ioctl(fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP) == -1) { + report_error("ioctl(PERF_EVENT_IOC_DISABLE)"); + } - if (read(fd, temp_result_vec.data(), temp_result_vec.size() * 8) == -1) { - report_error("read"); + if (read(fd, temp_result_vec.data(), temp_result_vec.size() * 8) == -1) { + report_error("read"); + } } // our actual results are in slots 1,3,5, ... of this structure // we really should be checking our ids obtained earlier to be safe diff --git a/benchmark/parse_stream.cpp b/benchmark/parse_stream.cpp index 3153d8e79..dee81c273 100755 --- a/benchmark/parse_stream.cpp +++ b/benchmark/parse_stream.cpp @@ -36,7 +36,7 @@ int main (int argc, char *argv[]){ for (auto i = 0; i < 3; i++) { //Actual test simdjson::dom::parser parser; - simdjson::error_code alloc_error = parser.set_capacity(p.size()); + simdjson::error_code alloc_error = parser.allocate(p.size()); if (alloc_error) { std::cerr << alloc_error << std::endl; return EXIT_FAILURE; diff --git a/benchmark/statisticalmodel.cpp b/benchmark/statisticalmodel.cpp index 8b7515653..38e575ccd 100644 --- a/benchmark/statisticalmodel.cpp +++ b/benchmark/statisticalmodel.cpp @@ -161,7 +161,7 @@ int main(int argc, char *argv[]) { #ifdef __linux__ simdjson::dom::parser parser; const simdjson::implementation &stage_parser = *simdjson::active_implementation; - simdjson::error_code alloc_error = parser.set_capacity(p.size()); + simdjson::error_code alloc_error = parser.allocate(p.size()); if (alloc_error) { std::cerr << alloc_error << std::endl; return EXIT_FAILURE; diff --git a/doc/performance.md b/doc/performance.md index 34fa2dcfd..5fee8aea5 100644 --- a/doc/performance.md +++ b/doc/performance.md @@ -77,7 +77,7 @@ without bound: ```c++ dom::parser parser(0); // This parser will refuse to automatically grow capacity - simdjson::error_code allocate_error = parser.set_capacity(1024*1024); // This allocates enough capacity to handle documents <= 1MB + simdjson::error_code allocate_error = parser.allocate(1024*1024); // This allocates enough capacity to handle documents <= 1MB if (allocate_error) { cerr << allocate_error << endl; exit(1); } for (web_request request : listen()) { diff --git a/include/simdjson/document.h b/include/simdjson/document.h index c3c678bf0..5f0340af5 100644 --- a/include/simdjson/document.h +++ b/include/simdjson/document.h @@ -350,7 +350,7 @@ public: std::unique_ptr string_buf; private: - inline error_code set_capacity(size_t len) noexcept; + inline error_code allocate(size_t len) noexcept; template friend class simdjson::minify; friend class parser; @@ -609,13 +609,10 @@ public: * @param max_capacity The maximum document length the parser can automatically handle. The parser * will allocate more capacity on an as needed basis (when it sees documents too big to handle) * up to this amount. The parser still starts with zero capacity no matter what this number is: - * to allocate an initial capacity, call set_capacity() after constructing the parser. Defaults - * to SIMDJSON_MAXSIZE_BYTES (the largest single document simdjson can process). - * @param max_depth The maximum depth--number of nested objects and arrays--this parser can handle. - * This will not be allocated until parse() is called for the first time. Defaults to - * DEFAULT_MAX_DEPTH. + * to allocate an initial capacity, call allocate() after constructing the parser. + * Defaults to SIMDJSON_MAXSIZE_BYTES (the largest single document simdjson can process). */ - really_inline parser(size_t max_capacity = SIMDJSON_MAXSIZE_BYTES, size_t max_depth = DEFAULT_MAX_DEPTH) noexcept; + really_inline parser(size_t max_capacity = SIMDJSON_MAXSIZE_BYTES) noexcept; /** * Take another parser's buffers and state. @@ -835,6 +832,30 @@ public: /** @private We do not want to allow implicit conversion from C string to std::string. */ really_inline simdjson_result parse_many(const char *buf, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept = delete; + /** + * Ensure this parser has enough memory to process JSON documents up to `capacity` bytes in length + * and `max_depth` depth. + * + * @param capacity The new capacity. + * @param max_depth The new max_depth. Defaults to DEFAULT_MAX_DEPTH. + * @return The error, if there is one. + */ + WARN_UNUSED inline error_code allocate(size_t capacity, size_t max_depth = DEFAULT_MAX_DEPTH) noexcept; + + /** + * @private deprecated because it returns bool instead of error_code, which is our standard for + * failures. Use allocate() instead. + * + * Ensure this parser has enough memory to process JSON documents up to `capacity` bytes in length + * and `max_depth` depth. + * + * @param capacity The new capacity. + * @param max_depth The new max_depth. Defaults to DEFAULT_MAX_DEPTH. + * @return true if successful, false if allocation failed. + */ + [[deprecated("Use allocate() instead.")]] + WARN_UNUSED inline bool allocate_capacity(size_t capacity, size_t max_depth = DEFAULT_MAX_DEPTH) noexcept; + /** * The largest document this parser can support without reallocating. * @@ -858,17 +879,6 @@ public: */ really_inline size_t max_depth() const noexcept; - /** - * Set capacity. This is the largest document this parser can support without reallocating. - * - * This will allocate or deallocate as necessary. - * - * @param capacity The new capacity, in bytes. - * - * @return MEMALLOC if unsuccessful, SUCCESS otherwise. - */ - WARN_UNUSED inline error_code set_capacity(size_t capacity) noexcept; - /** * Set max_capacity. This is the largest document this parser can automatically support. * @@ -880,32 +890,6 @@ public: */ really_inline void set_max_capacity(size_t max_capacity) noexcept; - /** - * Set the maximum level of nested object and arrays supported by this parser. - * - * This will allocate or deallocate as necessary. - * - * @param max_depth The new maximum depth, in bytes. - * - * @return MEMALLOC if unsuccessful, SUCCESS otherwise. - */ - WARN_UNUSED inline error_code set_max_depth(size_t max_depth) noexcept; - - /** - * @private @deprecated Use set_capacity() instead. - * - * Ensure this parser has enough memory to process JSON documents up to `capacity` bytes in length - * and `max_depth` depth. - * - * Equivalent to calling set_capacity() and set_max_depth(). - * - * @param capacity The new capacity. - * @param max_depth The new max_depth. Defaults to DEFAULT_MAX_DEPTH. - * @return true if successful, false if allocation failed. - */ - [[deprecated("Use set_capacity() instead.")]] - WARN_UNUSED inline bool allocate_capacity(size_t capacity, size_t max_depth = DEFAULT_MAX_DEPTH) noexcept; - /** @private Use the new DOM API instead */ class Iterator; /** @private Use simdjson_error instead */ @@ -987,13 +971,6 @@ public: really_inline bool on_number_double(double value) noexcept; ///< @private private: - /** - * The maximum document length this parser supports. - * - * Buffers are large enough to handle any document up to this length. - */ - size_t _capacity{0}; - /** * The maximum document length this parser will automatically support. * @@ -1001,12 +978,19 @@ private: */ size_t _max_capacity; + /** + * The maximum document length this parser supports. + * + * Buffers are large enough to handle any document up to this length. + */ + size_t _capacity{0}; + /** * The maximum depth (number of nested objects and arrays) supported by this parser. * * Defaults to DEFAULT_MAX_DEPTH. */ - size_t _max_depth; + size_t _max_depth{0}; /** * The loaded buffer (reused each time load() is called) diff --git a/include/simdjson/inline/document.h b/include/simdjson/inline/document.h index 5429538a0..dba261a01 100644 --- a/include/simdjson/inline/document.h +++ b/include/simdjson/inline/document.h @@ -193,7 +193,7 @@ inline element document::root() const noexcept { #define RETURN_ERROR(CODE, MESSAGE) return REPORT_ERROR((CODE), (MESSAGE)); WARN_UNUSED -inline error_code document::set_capacity(size_t capacity) noexcept { +inline error_code document::allocate(size_t capacity) noexcept { if (capacity == 0) { string_buf.reset(); tape.reset(); @@ -310,8 +310,8 @@ inline bool document::dump_raw_tape(std::ostream &os) const noexcept { // // parser inline implementation // -really_inline parser::parser(size_t max_capacity, size_t max_depth) noexcept - : _max_capacity{max_capacity}, _max_depth{max_depth}, loaded_bytes(nullptr, &aligned_free_char) {} +really_inline parser::parser(size_t max_capacity) noexcept + : _max_capacity{max_capacity}, loaded_bytes(nullptr, &aligned_free_char) {} inline bool parser::is_valid() const noexcept { return valid; } inline int parser::get_error_code() const noexcept { return error; } inline std::string parser::get_error_message() const noexcept { return error_message(error); } @@ -431,98 +431,97 @@ really_inline size_t parser::max_depth() const noexcept { } WARN_UNUSED -inline error_code parser::set_capacity(size_t capacity) noexcept { - if (_capacity == capacity) { - return SUCCESS; - } +inline error_code parser::allocate(size_t capacity, size_t max_depth) noexcept { + // + // If capacity has changed, reallocate capacity-based buffers + // + if (_capacity != capacity) { + // Set capacity to 0 until we finish, in case there's an error + _capacity = 0; - // Set capacity to 0 until we finish, in case there's an error - _capacity = 0; + // + // Reallocate the document + // + error_code err = doc.allocate(capacity); + if (err) { return err; } + + // + // Don't allocate 0 bytes, just return. + // + if (capacity == 0) { + structural_indexes.reset(); + return SUCCESS; + } + + // + // Initialize stage 1 output + // + uint32_t max_structures = ROUNDUP_N(capacity, 64) + 2 + 7; + structural_indexes.reset( new (std::nothrow) uint32_t[max_structures] ); // TODO realloc + if (!structural_indexes) { + return MEMALLOC; + } + + _capacity = capacity; // - // Reallocate the document + // If capacity hasn't changed, but the document was taken, allocate a new document. // - error_code err = doc.set_capacity(capacity); - if (err) { return err; } - - // - // Don't allocate 0 bytes, just return. - // - if (capacity == 0) { - structural_indexes.reset(); - return SUCCESS; + } else if (!doc.tape) { + error_code err = doc.allocate(capacity); + if (err) { return err; } } // - // Initialize stage 1 output + // If max_depth has changed, reallocate those buffers // - uint32_t max_structures = ROUNDUP_N(capacity, 64) + 2 + 7; - structural_indexes.reset( new (std::nothrow) uint32_t[max_structures] ); // TODO realloc - if (!structural_indexes) { - return MEMALLOC; - } + if (max_depth != _max_depth) { + _max_depth = 0; - _capacity = capacity; + if (max_depth == 0) { + ret_address.reset(); + containing_scope_offset.reset(); + return SUCCESS; + } + + // + // Initialize stage 2 state + // + containing_scope_offset.reset(new (std::nothrow) uint32_t[max_depth]); // TODO realloc + #ifdef SIMDJSON_USE_COMPUTED_GOTO + ret_address.reset(new (std::nothrow) void *[max_depth]); + #else + ret_address.reset(new (std::nothrow) char[max_depth]); + #endif + + if (!ret_address || !containing_scope_offset) { + // Could not allocate memory + return MEMALLOC; + } + + _max_depth = max_depth; + } return SUCCESS; } +WARN_UNUSED +inline bool parser::allocate_capacity(size_t capacity, size_t max_depth) noexcept { + return !allocate(capacity, max_depth); +} + really_inline void parser::set_max_capacity(size_t max_capacity) noexcept { _max_capacity = max_capacity; } -WARN_UNUSED inline error_code parser::set_max_depth(size_t max_depth) noexcept { - if (max_depth == _max_depth && ret_address) { return SUCCESS; } - - _max_depth = 0; - - if (max_depth == 0) { - ret_address.reset(); - containing_scope_offset.reset(); - return SUCCESS; - } - - // - // Initialize stage 2 state - // - containing_scope_offset.reset(new (std::nothrow) uint32_t[max_depth]); // TODO realloc -#ifdef SIMDJSON_USE_COMPUTED_GOTO - ret_address.reset(new (std::nothrow) void *[max_depth]); -#else - ret_address.reset(new (std::nothrow) char[max_depth]); -#endif - - if (!ret_address || !containing_scope_offset) { - // Could not allocate memory - return MEMALLOC; - } - - _max_depth = max_depth; - return SUCCESS; -} - -WARN_UNUSED inline bool parser::allocate_capacity(size_t capacity, size_t max_depth) noexcept { - return !set_capacity(capacity) && !set_max_depth(max_depth); -} - inline error_code parser::ensure_capacity(size_t desired_capacity) noexcept { // If we don't have enough capacity, (try to) automatically bump it. - if (unlikely(desired_capacity > capacity())) { + // If the document was taken, reallocate that too. + // Both in one if statement to minimize unlikely branching. + if (unlikely(desired_capacity > capacity() || !doc.tape)) { if (desired_capacity > max_capacity()) { return error = CAPACITY; } - - error = set_capacity(desired_capacity); - if (error) { return error; } - } - - // Allocate depth-based buffers if they aren't already. - error = set_max_depth(max_depth()); - if (error) { return error; } - - // If the last doc was taken, we need to allocate a new one - if (!doc.tape) { - error = doc.set_capacity(desired_capacity); - if (error) { return error; } + return allocate(desired_capacity, _max_depth > 0 ? _max_depth : DEFAULT_MAX_DEPTH); } return SUCCESS; diff --git a/tests/readme_examples.cpp b/tests/readme_examples.cpp index c94ad5e72..3b36d24fb 100644 --- a/tests/readme_examples.cpp +++ b/tests/readme_examples.cpp @@ -125,7 +125,7 @@ void performance_2() { // The web_request part of this is aspirational, so we compile as much as we can here void performance_3() { dom::parser parser(0); // This parser will refuse to automatically grow capacity - simdjson::error_code allocate_error = parser.set_capacity(1024*1024); // This allocates enough capacity to handle documents <= 1MB + simdjson::error_code allocate_error = parser.allocate(1024*1024); // This allocates enough capacity to handle documents <= 1MB if (allocate_error) { cerr << allocate_error << endl; exit(1); } // for (web_request request : listen()) {