From d140bc23f547e7cca43f85bf0ba1004e26e228e3 Mon Sep 17 00:00:00 2001 From: John Keiser Date: Fri, 6 Mar 2020 15:40:59 -0800 Subject: [PATCH] Automatically allocate memory as needed in parse --- README.md | 43 +++- benchmark/bench_parse_call.cpp | 6 +- benchmark/benchmarker.h | 6 +- benchmark/parse_stream.cpp | 6 +- include/simdjson/document.h | 252 ++++++++++++++++------ include/simdjson/implementation.h | 2 +- include/simdjson/inline/document.h | 84 +++++--- include/simdjson/inline/document_stream.h | 31 +-- include/simdjson/jsonparser.h | 5 - tests/basictests.cpp | 22 +- tests/jsoncheck.cpp | 5 - tests/numberparsingcheck.cpp | 5 - tests/pointercheck.cpp | 1 - tests/readme_examples.cpp | 33 ++- tests/singleheadertest.cpp | 3 - tests/stringparsingcheck.cpp | 5 - tools/json2json.cpp | 5 - tools/jsonpointer.cpp | 5 - 18 files changed, 317 insertions(+), 202 deletions(-) diff --git a/README.md b/README.md index fd9b4e672..4dcad5a6a 100644 --- a/README.md +++ b/README.md @@ -168,23 +168,48 @@ document doc = document::parse(get_corpus(filename)); doc.print_json(cout); ``` -If you're using simdjson to parse multiple documents, or in a loop, you should allocate a parser once and reuse it (allocation is slow, do it as little as possible!): +### Reusing the parser for maximum efficiency + +If you're using simdjson to parse multiple documents, or in a loop, you should make a parser once +and reuse it. simdjson will allocate and retain internal buffers between parses, keeping buffers +hot in cache and keeping allocation to a minimum. ```c++ -// Allocate a parser big enough for all files document::parser parser; -if (!parser.allocate_capacity(1024*1024)) { exit(1); } - -// Read files with the parser, one by one for (padded_string json : { string("[1, 2, 3]"), string("true"), string("[ true, false ]") }) { - cout << "Parsing " << json.data() << " ..." << endl; - auto [doc, error] = parser.parse(json); - if (error) { cerr << "Error: " << error << endl; exit(1); } + document& doc = parser.parse(json); doc.print_json(cout); - cout << endl; } ``` +If you are running a server loop and want to limit the document size to keep server memory constant, +you can set a maximum capacity: + +```c++ +document::parser parser(1024*1024); // Set max capacity to 1MB +for (int i=0;istage1((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/parse_stream.cpp b/benchmark/parse_stream.cpp index f312796ca..07084c724 100755 --- a/benchmark/parse_stream.cpp +++ b/benchmark/parse_stream.cpp @@ -39,9 +39,9 @@ int main (int argc, char *argv[]){ for (auto i = 0; i < 3; i++) { //Actual test simdjson::document::parser parser; - bool allocok = parser.allocate_capacity(p.size()); - if (!allocok) { - std::cerr << "failed to allocate memory" << std::endl; + simdjson::error_code alloc_error = parser.set_capacity(p.size()); + if (alloc_error) { + std::cerr << alloc_error << std::endl; return EXIT_FAILURE; } std::istringstream ss(std::string(p.data(), p.size())); diff --git a/include/simdjson/document.h b/include/simdjson/document.h index d1188416d..e9f5c657b 100644 --- a/include/simdjson/document.h +++ b/include/simdjson/document.h @@ -199,7 +199,7 @@ public: private: class tape_ref; enum class tape_type; - bool set_capacity(size_t len); + inline error_code set_capacity(size_t len) noexcept; }; // class document /** @@ -818,8 +818,8 @@ private: /** * A persistent document parser. * - * Use this if you intend to parse more than one document. It holds the internal memory necessary - * to do parsing, as well as memory for a single document that is overwritten on each parse. + * The parser is designed to be reused, holding the internal buffers necessary to do parsing, + * as well as memory for a single document. The parsed document is overwritten on each parse. * * This class cannot be copied, only moved, to avoid unintended allocations. * @@ -828,9 +828,20 @@ private: class document::parser { public: /** - * Create a JSON parser with zero capacity. Call allocate_capacity() to initialize it. + * Create a JSON parser. + * + * The new parser will have zero capacity. + * + * @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. */ - parser()=default; + really_inline parser(size_t max_capacity = SIMDJSON_MAXSIZE_BYTES, size_t max_depth = DEFAULT_MAX_DEPTH) noexcept; ~parser()=default; /** @@ -849,71 +860,136 @@ public: parser &operator=(const document::parser &) = delete; // Disallow copying /** - * Parse a JSON document and return a reference to it. + * Parse a JSON document and return a temporary reference to it. + * + * document::parser parser; + * const document &doc = parser.parse(buf, len); + * + * ### IMPORTANT: Document Lifetime * * The JSON document still lives in the parser: this is the most efficient way to parse JSON * documents because it reuses the same buffers, but you *must* use the document before you * destroy the parser or call parse() again. * + * ### REQUIRED: Buffer Padding + * * The buffer must have at least SIMDJSON_PADDING extra allocated bytes. It does not matter what - * those bytes are initialized to, as long as they are allocated. If realloc_if_needed is true, - * it is assumed that the buffer does *not* have enough padding, and it is reallocated, enlarged - * and copied before parsing. + * those bytes are initialized to, as long as they are allocated. + * + * If realloc_if_needed is true, it is assumed that the buffer does *not* have enough padding, + * and it is copied into an enlarged temporary buffer before parsing. + * + * ### Parser Capacity + * + * If the parser's current capacity is less than len, it will allocate enough capacity + * to handle it (up to max_capacity). * * @param buf The JSON to parse. Must have at least len + SIMDJSON_PADDING allocated bytes, unless * realloc_if_needed is true. * @param len The length of the JSON. * @param realloc_if_needed Whether to reallocate and enlarge the JSON buffer to add padding. - * @return the document, or an error if the JSON is invalid. + * @return The document, or an error: + * - MEMALLOC if realloc_if_needed is true or the parser does not have enough capacity, + * and memory allocation fails. + * - CAPACITY if the parser does not have enough capacity and len > max_capacity. + * - other json errors if parsing fails. */ inline doc_ref_result parse(const uint8_t *buf, size_t len, bool realloc_if_needed = true) noexcept; /** - * Parse a JSON document and return a reference to it. + * Parse a JSON document and return a temporary reference to it. + * + * document::parser parser; + * const document &doc = parser.parse(buf, len); + * + * ### IMPORTANT: Document Lifetime * * The JSON document still lives in the parser: this is the most efficient way to parse JSON * documents because it reuses the same buffers, but you *must* use the document before you * destroy the parser or call parse() again. * + * ### REQUIRED: Buffer Padding + * * The buffer must have at least SIMDJSON_PADDING extra allocated bytes. It does not matter what - * those bytes are initialized to, as long as they are allocated. If realloc_if_needed is true, - * it is assumed that the buffer does *not* have enough padding, and it is reallocated, enlarged - * and copied before parsing. + * those bytes are initialized to, as long as they are allocated. + * + * If realloc_if_needed is true, it is assumed that the buffer does *not* have enough padding, + * and it is copied into an enlarged temporary buffer before parsing. + * + * ### Parser Capacity + * + * If the parser's current capacity is less than len, it will allocate enough capacity + * to handle it (up to max_capacity). * * @param buf The JSON to parse. Must have at least len + SIMDJSON_PADDING allocated bytes, unless * realloc_if_needed is true. * @param len The length of the JSON. * @param realloc_if_needed Whether to reallocate and enlarge the JSON buffer to add padding. - * @return the document, or an error if the JSON is invalid. + * @return The document, or an error: + * - MEMALLOC if realloc_if_needed is true or the parser does not have enough capacity, + * and memory allocation fails. + * - CAPACITY if the parser does not have enough capacity and len > max_capacity. + * - other json errors if parsing fails. */ really_inline doc_ref_result parse(const char *buf, size_t len, bool realloc_if_needed = true) noexcept; /** - * Parse a JSON document and return a reference to it. + * Parse a JSON document and return a temporary reference to it. + * + * document::parser parser; + * const document &doc = parser.parse(s); + * + * ### IMPORTANT: Document Lifetime * * The JSON document still lives in the parser: this is the most efficient way to parse JSON * documents because it reuses the same buffers, but you *must* use the document before you * destroy the parser or call parse() again. * + * ### REQUIRED: Buffer Padding + * * The buffer must have at least SIMDJSON_PADDING extra allocated bytes. It does not matter what - * those bytes are initialized to, as long as they are allocated. If `str.capacity() - str.size() - * < SIMDJSON_PADDING`, the string will be copied to a string with larger capacity before parsing. + * those bytes are initialized to, as long as they are allocated. + * + * If s.capacity() is less than SIMDJSON_PADDING, the string will be copied into an enlarged + * temporary buffer before parsing. + * + * ### Parser Capacity + * + * If the parser's current capacity is less than len, it will allocate enough capacity + * to handle it (up to max_capacity). * * @param s The JSON to parse. Must have at least len + SIMDJSON_PADDING allocated bytes, or * a new string will be created with the extra padding. - * @return the document, or an error if the JSON is invalid. + * @return The document, or an error: + * - MEMALLOC if the string does not have enough padding or the parser does not have + * enough capacity, and memory allocation fails. + * - CAPACITY if the parser does not have enough capacity and len > max_capacity. + * - other json errors if parsing fails. */ really_inline doc_ref_result parse(const std::string &s) noexcept; /** - * Parse a JSON document and return a reference to it. + * Parse a JSON document and return a temporary reference to it. + * + * document::parser parser; + * const document &doc = parser.parse(s); + * + * ### IMPORTANT: Document Lifetime * * The JSON document still lives in the parser: this is the most efficient way to parse JSON * documents because it reuses the same buffers, but you *must* use the document before you * destroy the parser or call parse() again. * + * ### Parser Capacity + * + * If the parser's current capacity is less than batch_size, it will allocate enough capacity + * to handle it (up to max_capacity). + * * @param s The JSON to parse. - * @return the document, or an error if the JSON is invalid. + * @return The document, or an error: + * - MEMALLOC if the parser does not have enough capacity and memory allocation fails. + * - CAPACITY if the parser does not have enough capacity and len > max_capacity. + * - other json errors if parsing fails. */ really_inline doc_ref_result parse(const padded_string &s) noexcept; @@ -965,8 +1041,8 @@ public: * * ### Parser Capacity * - * If the parser is unallocated, it will be auto-allocated to batch_size. If it is already - * allocated, it must have a capacity at least as large as batch_size. + * If the parser's current capacity is less than batch_size, it will allocate enough capacity + * to handle it (up to max_capacity). * * @param buf The concatenated JSON to parse. Must have at least len + SIMDJSON_PADDING allocated bytes. * @param len The length of the concatenated JSON. @@ -976,8 +1052,8 @@ public: * Defaults to 10MB, which has been a reasonable sweet spot in our tests. * @return The stream. If there is an error, it will be returned during iteration. An empty input * will yield 0 documents rather than an EMPTY error. Errors: - * - MEMALLOC if the parser is unallocated and memory allocation fails - * - CAPACITY if the parser already has a capacity, and it is less than batch_size + * - MEMALLOC if the parser does not have enough capacity and memory allocation fails + * - CAPACITY if the parser does not have enough capacity and batch_size > max_capacity. * - other json errors if parsing fails. */ inline stream parse_many(const uint8_t *buf, size_t len, size_t batch_size = 1000000) noexcept; @@ -1027,8 +1103,8 @@ public: * * ### Parser Capacity * - * If the parser is unallocated, it will be auto-allocated to batch_size. If it is already - * allocated, it must have a capacity at least as large as batch_size. + * If the parser's current capacity is less than batch_size, it will allocate enough capacity + * to handle it (up to max_capacity). * * @param buf The concatenated JSON to parse. Must have at least len + SIMDJSON_PADDING allocated bytes. * @param len The length of the concatenated JSON. @@ -1038,8 +1114,8 @@ public: * Defaults to 10MB, which has been a reasonable sweet spot in our tests. * @return The stream. If there is an error, it will be returned during iteration. An empty input * will yield 0 documents rather than an EMPTY error. Errors: - * - MEMALLOC if the parser is unallocated and memory allocation fails - * - CAPACITY if the parser already has a capacity, and it is less than batch_size + * - MEMALLOC if the parser does not have enough capacity and memory allocation fails + * - CAPACITY if the parser does not have enough capacity and batch_size > max_capacity. * - other json errors if parsing fails */ inline stream parse_many(const char *buf, size_t len, size_t batch_size = 1000000) noexcept; @@ -1089,18 +1165,18 @@ public: * * ### Parser Capacity * - * If the parser is unallocated, it will be auto-allocated to batch_size. If it is already - * allocated, it must have a capacity at least as large as batch_size. + * If the parser's current capacity is less than batch_size, it will allocate enough capacity + * to handle it (up to max_capacity). * * @param s The concatenated JSON to parse. Must have at least len + SIMDJSON_PADDING allocated bytes. * @param batch_size The batch size to use. MUST be larger than the largest document. The sweet * spot is cache-related: small enough to fit in cache, yet big enough to * parse as many documents as possible in one tight loop. * Defaults to 10MB, which has been a reasonable sweet spot in our tests. - * @return he stream. If there is an error, it will be returned during iteration. An empty input + * @return The stream. If there is an error, it will be returned during iteration. An empty input * will yield 0 documents rather than an EMPTY error. Errors: - * - MEMALLOC if the parser is unallocated and memory allocation fails - * - CAPACITY if the parser already has a capacity, and it is less than batch_size + * - MEMALLOC if the parser does not have enough capacity and memory allocation fails + * - CAPACITY if the parser does not have enough capacity and batch_size > max_capacity. * - other json errors if parsing fails */ inline stream parse_many(const std::string &s, size_t batch_size = 1000000) noexcept; @@ -1138,11 +1214,6 @@ public: * cout << std::string(doc["title"]) << endl; * } * - * ### REQUIRED: Buffer Padding - * - * The buffer must have at least SIMDJSON_PADDING extra allocated bytes. It does not matter what - * those bytes are initialized to, as long as they are allocated. - * * ### Threads * * When compiled with SIMDJSON_THREADS_ENABLED, this method will use a single thread under the @@ -1150,18 +1221,18 @@ public: * * ### Parser Capacity * - * If the parser is unallocated, it will be auto-allocated to batch_size. If it is already - * allocated, it must have a capacity at least as large as batch_size. + * If the parser's current capacity is less than batch_size, it will allocate enough capacity + * to handle it (up to max_capacity). * * @param s The concatenated JSON to parse. * @param batch_size The batch size to use. MUST be larger than the largest document. The sweet * spot is cache-related: small enough to fit in cache, yet big enough to * parse as many documents as possible in one tight loop. * Defaults to 10MB, which has been a reasonable sweet spot in our tests. - * @return he stream. If there is an error, it will be returned during iteration. An empty input + * @return The stream. If there is an error, it will be returned during iteration. An empty input * will yield 0 documents rather than an EMPTY error. Errors: - * - MEMALLOC if the parser is unallocated and memory allocation fails - * - CAPACITY if the parser already has a capacity, and it is less than batch_size + * - MEMALLOC if the parser does not have enough capacity and memory allocation fails + * - CAPACITY if the parser does not have enough capacity and batch_size > max_capacity. * - other json errors if parsing fails */ inline stream parse_many(const padded_string &s, size_t batch_size = 1000000) noexcept; @@ -1170,20 +1241,72 @@ public: really_inline doc_ref_result parse_many(const char *buf, size_t batch_size = 1000000) noexcept = delete; /** - * Current capacity: the largest document this parser can support without reallocating. + * The largest document this parser can automatically support. + * + * The parser may reallocate internal buffers as needed up to this amount. + * + * @return Maximum capacity, in bytes. + */ + really_inline size_t max_capacity() const noexcept; + + /** + * The largest document this parser can support without reallocating. + * + * @return Current capacity, in bytes. */ really_inline size_t capacity() const noexcept; /** * The maximum level of nested object and arrays supported by this parser. + * + * @return Maximum depth, in bytes. */ really_inline size_t max_depth() const noexcept; + /** + * Set max_capacity. This is the largest document this parser can automatically support. + * + * The parser may reallocate internal buffers as needed up to this amount. + * + * This call will not allocate or deallocate, even if capacity is currently above max_capacity. + * + * @param max_capacity The new maximum capacity, in bytes. + */ + really_inline void set_max_capacity(size_t max_capacity) 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 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; + /** * 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. */ - WARN_UNUSED inline bool allocate_capacity(size_t capacity, size_t max_depth = DEFAULT_MAX_DEPTH); + WARN_UNUSED inline bool allocate_capacity(size_t capacity, size_t max_depth = DEFAULT_MAX_DEPTH) noexcept; // type aliases for backcompat using Iterator = document::iterator; @@ -1258,13 +1381,6 @@ public: really_inline bool on_number_s64(int64_t value) noexcept; really_inline bool on_number_u64(uint64_t value) noexcept; really_inline bool on_number_double(double value) noexcept; - // - // Called before a parse is initiated. - // - // - Returns CAPACITY if the document is too large - // - Returns MEMALLOC if we needed to allocate memory and could not - // - WARN_UNUSED inline error_code init_parse(size_t len) noexcept; private: // @@ -1274,12 +1390,19 @@ private: // size_t _capacity{0}; + // + // The maximum document length this parser will automatically support. + // + // The parser will not be automatically allocated above this amount. + // + size_t _max_capacity; + // // The maximum depth (number of nested objects and arrays) supported by this parser. // // Defaults to DEFAULT_MAX_DEPTH. // - size_t _max_depth{0}; + size_t _max_depth; // all nodes are stored on the doc.tape using a 64-bit word. // @@ -1298,28 +1421,15 @@ private: inline void write_tape(uint64_t val, tape_type t) noexcept; inline void annotate_previous_loc(uint32_t saved_loc, uint64_t val) noexcept; - // - // Set the current capacity: the largest document this parser can support without reallocating. - // - // This will allocate *or deallocate* as necessary. - // - // Returns false if allocation fails. - // - inline WARN_UNUSED bool set_capacity(size_t capacity); - - // - // Set the maximum level of nested object and arrays supported by this parser. - // - // This will allocate *or deallocate* as necessary. - // - // Returns false if allocation fails. - // - inline WARN_UNUSED bool set_max_depth(size_t max_depth); + // Ensure we have enough capacity to handle at least desired_capacity bytes, + // and auto-allocate if not. + inline error_code ensure_capacity(size_t desired_capacity) noexcept; // Used internally to get the document inline const document &get_document() const noexcept(false); template friend class document_iterator; + friend class document::stream; }; // class parser } // namespace simdjson diff --git a/include/simdjson/implementation.h b/include/simdjson/implementation.h index 53bb16dd6..35bc872c5 100644 --- a/include/simdjson/implementation.h +++ b/include/simdjson/implementation.h @@ -45,7 +45,7 @@ public: virtual uint32_t required_instruction_sets() const { return _required_instruction_sets; }; /** - * Run a full document parse (init_parse, stage1 and stage2). + * Run a full document parse (ensure_capacity, stage1 and stage2). * * Overridden by each implementation. * diff --git a/include/simdjson/inline/document.h b/include/simdjson/inline/document.h index 7352a1b41..e11b2bd32 100644 --- a/include/simdjson/inline/document.h +++ b/include/simdjson/inline/document.h @@ -198,28 +198,25 @@ inline document::element_result document::operator[](const ch inline document::doc_result document::parse(const uint8_t *buf, size_t len, bool realloc_if_needed) noexcept { document::parser parser; - if (!parser.allocate_capacity(len)) { - return MEMALLOC; - } auto [doc, error] = parser.parse(buf, len, realloc_if_needed); return document::doc_result((document &&)doc, error); } really_inline document::doc_result document::parse(const char *buf, size_t len, bool realloc_if_needed) noexcept { - return parse((const uint8_t *)buf, len, realloc_if_needed); + return parse((const uint8_t *)buf, len, realloc_if_needed); } really_inline document::doc_result document::parse(const std::string &s) noexcept { - return parse(s.data(), s.length(), s.capacity() - s.length() < SIMDJSON_PADDING); + return parse(s.data(), s.length(), s.capacity() - s.length() < SIMDJSON_PADDING); } really_inline document::doc_result document::parse(const padded_string &s) noexcept { - return parse(s.data(), s.length(), false); + return parse(s.data(), s.length(), false); } WARN_UNUSED -inline bool document::set_capacity(size_t capacity) { +inline error_code document::set_capacity(size_t capacity) noexcept { if (capacity == 0) { string_buf.reset(); tape.reset(); - return true; + return SUCCESS; } // a pathological input like "[[[[..." would generate len tape elements, so @@ -233,7 +230,7 @@ inline bool document::set_capacity(size_t capacity) { size_t string_capacity = ROUNDUP_N(5 * capacity / 3 + 32, 64); string_buf.reset( new (std::nothrow) uint8_t[string_capacity]); tape.reset(new (std::nothrow) uint64_t[tape_capacity]); - return string_buf && tape; + return string_buf && tape ? SUCCESS : MEMALLOC; } inline bool document::print_json(std::ostream &os, size_t max_depth) const noexcept { @@ -462,6 +459,10 @@ inline document::doc_result::operator document() noexcept(false) { // // document::parser inline implementation // +really_inline document::parser::parser(size_t max_capacity, size_t max_depth) noexcept + : _max_capacity{max_capacity}, _max_depth{max_depth} { + +} inline bool document::parser::is_valid() const noexcept { return valid; } inline int document::parser::get_error_code() const noexcept { return error; } inline std::string document::parser::get_error_message() const noexcept { return error_message(int(error)); } @@ -479,7 +480,7 @@ inline const document &document::parser::get_document() const noexcept(false) { } inline document::doc_ref_result document::parser::parse(const uint8_t *buf, size_t len, bool realloc_if_needed) noexcept { - error_code code = init_parse(len); + error_code code = ensure_capacity(len); if (code) { return document::doc_ref_result(doc, code); } if (realloc_if_needed) { @@ -526,17 +527,17 @@ inline document::stream document::parser::parse_many(const padded_string &s, siz really_inline size_t document::parser::capacity() const noexcept { return _capacity; } +really_inline size_t document::parser::max_capacity() const noexcept { + return _max_capacity; +} really_inline size_t document::parser::max_depth() const noexcept { return _max_depth; } -WARN_UNUSED inline bool document::parser::allocate_capacity(size_t capacity, size_t max_depth) { - return set_capacity(capacity) && set_max_depth(max_depth); -} WARN_UNUSED -inline bool document::parser::set_capacity(size_t capacity) { +inline error_code document::parser::set_capacity(size_t capacity) noexcept { if (_capacity == capacity) { - return true; + return SUCCESS; } // Set capacity to 0 until we finish, in case there's an error @@ -545,16 +546,15 @@ inline bool document::parser::set_capacity(size_t capacity) { // // Reallocate the document // - if (!doc.set_capacity(capacity)) { - return false; - } + 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 true; + return SUCCESS; } // @@ -563,20 +563,26 @@ inline bool document::parser::set_capacity(size_t capacity) { 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 false; + return MEMALLOC; } _capacity = capacity; - return true; + return SUCCESS; } -WARN_UNUSED inline bool document::parser::set_max_depth(size_t max_depth) { +really_inline void document::parser::set_max_capacity(size_t max_capacity) noexcept { + _max_capacity = max_capacity; +} + +WARN_UNUSED inline error_code document::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 true; + return SUCCESS; } // @@ -591,24 +597,38 @@ WARN_UNUSED inline bool document::parser::set_max_depth(size_t max_depth) { if (!ret_address || !containing_scope_offset) { // Could not allocate memory - return false; + return MEMALLOC; } _max_depth = max_depth; - return true; + return SUCCESS; } -WARN_UNUSED -inline error_code document::parser::init_parse(size_t len) noexcept { - if (len > capacity()) { - return error = CAPACITY; +WARN_UNUSED inline bool document::parser::allocate_capacity(size_t capacity, size_t max_depth) noexcept { + return !set_capacity(capacity) && !set_max_depth(max_depth); +} + +inline error_code document::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 (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) { - if (!doc.set_capacity(len)) { - return error = MEMALLOC; - } + error = doc.set_capacity(desired_capacity); + if (error) { return error; } } + return SUCCESS; } diff --git a/include/simdjson/inline/document_stream.h b/include/simdjson/inline/document_stream.h index 1b622d011..f7d84a9c7 100644 --- a/include/simdjson/inline/document_stream.h +++ b/include/simdjson/inline/document_stream.h @@ -148,21 +148,11 @@ really_inline bool document::stream::iterator::operator!=(const document::stream // threaded version of json_parse // todo: simplify this code further inline error_code document::stream::json_parse() noexcept { - // TODO we should bump the parser *anytime* capacity is less than batch size, not just 0. - if (unlikely(parser.capacity() == 0)) { - const bool allocok = parser.allocate_capacity(_batch_size); - if (!allocok) { - return simdjson::MEMALLOC; - } - } else if (unlikely(parser.capacity() < _batch_size)) { - return simdjson::CAPACITY; - } - if (unlikely(parser_thread.capacity() < _batch_size)) { - const bool allocok_thread = parser_thread.allocate_capacity(_batch_size); - if (!allocok_thread) { - return simdjson::MEMALLOC; - } - } + error = parser.ensure_capacity(_batch_size); + if (error) { return error; } + error = parser_thread.ensure_capacity(_batch_size); + if (error) { return error; } + if (unlikely(load_next_batch)) { // First time loading if (!stage_1_thread.joinable()) { @@ -240,14 +230,9 @@ inline error_code document::stream::json_parse() noexcept { // single-threaded version of json_parse inline error_code document::stream::json_parse() noexcept { - if (unlikely(parser.capacity() == 0)) { - const bool allocok = parser.allocate_capacity(_batch_size); - if (!allocok) { - return MEMALLOC; - } - } else if (unlikely(parser.capacity() < _batch_size)) { - return CAPACITY; - } + error = parser.ensure_capacity(_batch_size); + if (error) { return error; } + if (unlikely(load_next_batch)) { advance(current_buffer_loc); n_bytes_parsed += current_buffer_loc; diff --git a/include/simdjson/jsonparser.h b/include/simdjson/jsonparser.h index 1ad6ce67f..dc08bd04f 100644 --- a/include/simdjson/jsonparser.h +++ b/include/simdjson/jsonparser.h @@ -35,11 +35,6 @@ inline int json_parse(const padded_string &s, document::parser &parser) noexcept WARN_UNUSED static document::parser build_parsed_json(const uint8_t *buf, size_t len, bool realloc_if_needed = true) noexcept { document::parser parser; - if (!parser.allocate_capacity(len)) { - parser.valid = false; - parser.error = MEMALLOC; - return parser; - } json_parse(buf, len, parser, realloc_if_needed); return parser; } diff --git a/tests/basictests.cpp b/tests/basictests.cpp index e2c5be91c..8477f6f25 100644 --- a/tests/basictests.cpp +++ b/tests/basictests.cpp @@ -33,10 +33,6 @@ inline uint64_t f64_ulp_dist(double a, double b) { bool number_test_small_integers() { char buf[1024]; simdjson::document::parser parser; - if (!parser.allocate_capacity(1024)) { - printf("allocation failure in number_test_small_integers\n"); - return false; - } for (int m = 10; m < 20; m++) { for (int i = -1024; i < 1024; i++) { auto n = sprintf(buf, "%*d", m, i); @@ -71,10 +67,6 @@ bool number_test_small_integers() { bool number_test_powers_of_two() { char buf[1024]; simdjson::document::parser parser; - if (!parser.allocate_capacity(1024)) { - printf("allocation failure in number_test\n"); - return false; - } int maxulp = 0; for (int i = -1075; i < 1024; ++i) {// large negative values should be zero. double expected = pow(2, i); @@ -138,10 +130,6 @@ bool number_test_powers_of_two() { bool number_test_powers_of_ten() { char buf[1024]; simdjson::document::parser parser; - if (!parser.allocate_capacity(1024)) { - printf("allocation failure in number_test\n"); - return false; - } for (int i = -1000000; i <= 308; ++i) {// large negative values should be zero. auto n = sprintf(buf,"1e%d", i); buf[n] = '\0'; @@ -247,12 +235,8 @@ bool stable_test() { static bool parse_json_message_issue467(char const* message, std::size_t len, size_t expectedcount) { simdjson::document::parser parser; size_t count = 0; - if (!parser.allocate_capacity(len)) { - std::cerr << "Failed to allocated memory for simdjson::document::parser" << std::endl; - return false; - } simdjson::padded_string str(message,len); - for (auto [doc, error] : parser.parse_many(str, parser.capacity())) { + for (auto [doc, error] : parser.parse_many(str, len)) { if (error) { std::cerr << "Failed with simdjson error= " << error << std::endl; return false; @@ -639,10 +623,6 @@ bool skyprophet_test() { maxsize = s.size(); } simdjson::document::parser parser; - if (!parser.allocate_capacity(maxsize)) { - printf("allocation failure in skyprophet_test\n"); - return false; - } size_t counter = 0; for (auto &rec : data) { if ((counter % 10000) == 0) { diff --git a/tests/jsoncheck.cpp b/tests/jsoncheck.cpp index c17135b28..1e4323ab3 100644 --- a/tests/jsoncheck.cpp +++ b/tests/jsoncheck.cpp @@ -73,11 +73,6 @@ bool validate(const char *dirname) { return EXIT_FAILURE; } simdjson::ParsedJson pj; - bool allocok = pj.allocate_capacity(p.size(), 1024); - if (!allocok) { - std::cerr << "can't allocate memory" << std::endl; - return false; - } ++how_many; const int parse_res = json_parse(p, pj); printf("%s\n", parse_res == 0 ? "ok" : "invalid"); diff --git a/tests/numberparsingcheck.cpp b/tests/numberparsingcheck.cpp index 4fb31b542..89c70fa28 100644 --- a/tests/numberparsingcheck.cpp +++ b/tests/numberparsingcheck.cpp @@ -180,11 +180,6 @@ bool validate(const char *dirname) { } // terrible hack but just to get it working simdjson::ParsedJson pj; - bool allocok = pj.allocate_capacity(p.size(), 1024); - if (!allocok) { - std::cerr << "can't allocate memory" << std::endl; - return false; - } float_count = 0; int_count = 0; invalid_count = 0; diff --git a/tests/pointercheck.cpp b/tests/pointercheck.cpp index 6a6affbb8..4d5365948 100644 --- a/tests/pointercheck.cpp +++ b/tests/pointercheck.cpp @@ -7,7 +7,6 @@ int main() { std::string json = "{\"/~01abc\": [0, {\"\\\\\\\" 0\": [\"value0\", \"value1\"]}]}"; simdjson::ParsedJson pj; - assert(pj.allocate_capacity(json.length())); simdjson::json_parse(json.c_str(), json.length(), pj); assert(pj.is_valid()); simdjson::ParsedJson::Iterator it(pj); diff --git a/tests/readme_examples.cpp b/tests/readme_examples.cpp index e3e4d2b97..f37ede656 100644 --- a/tests/readme_examples.cpp +++ b/tests/readme_examples.cpp @@ -45,14 +45,15 @@ void parser_parse() { // Allocate a parser big enough for all files document::parser parser; - if (!parser.allocate_capacity(1024*1024)) { exit(1); } + simdjson::error_code capacity_error = parser.set_capacity(1024*1024); + if (capacity_error) { cerr << "Error setting capacity: " << capacity_error << endl; exit(1); } // Read files with the parser, one by one for (padded_string json : { string("[1, 2, 3]"), string("true"), string("[ true, false ]") }) { cout << "Parsing " << json.data() << " ..." << endl; auto [doc, error] = parser.parse(json); if (error) { cerr << "Error: " << error << endl; exit(1); } - if (!doc.print_json(cout)) { exit(1); } + if (!doc.print_json(cout)) { cerr << "print failed!\n"; exit(1); } cout << endl; } } @@ -84,6 +85,32 @@ void parser_parse_many_exception() { } } +void parser_parse_max_capacity() { + int argc = 2; + padded_string argv[] { string("[1,2,3]"), string("true") }; + document::parser parser(1024*1024); // Set max capacity to 1MB + for (int i=0;i