Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Object members stored in std::multimap #1485

Merged
merged 3 commits into from
Apr 8, 2021
Merged

Conversation

ylavic
Copy link
Contributor

@ylavic ylavic commented Apr 11, 2019

Set RAPIDJSON_USE_MEMBERSMAP to use a (std::multi)map for object members.

When RAPIDJSON_USE_MEMBERSMAP is defined, an object Value will store
its members in an (re)allocated array of Members like before, but also
in an std::multimap<GenericValue::Data,SizeType> where the key and value
reference the corresponding Member by its Data and index in the array,
respectively, and in a relocatable manner.

The layout of the members map/array is now:
{multimap*}<>{capacity}<>{Member[capacity]}<>{multimap::iterator[capacity]}
where <> stands for the RAPIDJSON_ALIGN-ment of each part, if needed.

This layout needs to be reallocated when the current capacity is
exhausted, which requires to take care of the multimap and its iterators
explicitely. The multimap is allocated separately and only its pointer is
saved in this layout, so it can easily be restored in its new position.
As for the old/alive iterators, they must move to their new offset according
to the new capacity.

With this in place, it's immediate to get the multimap::iterator from a
MemberIterator and vice versa, thus the same complexity applies for the
operations with MemberIterator or MapIterator.

For FindMember() and RemoveMember(), the complexity drops from O(n) to
the multimap/rbtree's O(log n).
For EraseMember() it drops from O(n-m) to O((log n)-m), m representing
the move/copy of the trailing members.
For AddMember() though, the complexity grows from O(1) to O(log n) due to
the insertion in the multimap too.

Consequently parsing will be slower, up to ~20% measured in perftests on
my laptop (since it's mainly composed of insertions). But later work on
the Document (usually the goal of parsing...) will be much faster; the
new DocumentFind perftest included in this commit is 8 times faster with
RAPIDJSON_USE_MEMBERSMAP (still on my laptop). Overall the tests are 4%
slower (mainly composed of parsing), and notably 15% slower for schemas
parsing/validation (which supposedly comes from the larger JSON files
parsing, still). As a side note, when RAPIDJSON_USE_MEMBERSMAP is not
defined, this commit does nothing (same results for perftest with regard
to previous versions).

Finally, the multimap is allocated and constructed using StdAllocator,
so they will use the same Allocator than for any other Value allocation,
and thus will benefit from the same performance/safety/security/whatever
provided by the user given Allocator.

@ylavic
Copy link
Contributor Author

ylavic commented Apr 11, 2019

Some numbers:

$ diff -u0 perf-{old,new}-opt.txt
--- perf-old-opt.txt	2019-04-10 18:59:21.424867301 +0200
+++ perf-new-opt.txt	2019-04-10 18:59:59.093065660 +0200
@@ -5 +5 @@
-[       OK ] RapidJson.ReaderParseInsitu_DummyHandler_SSE42 (363 ms)
+[       OK ] RapidJson.ReaderParseInsitu_DummyHandler_SSE42 (375 ms)
@@ -7 +7 @@
-[       OK ] RapidJson.ReaderParseInsitu_DummyHandler_ValidateEncoding_SSE42 (758 ms)
+[       OK ] RapidJson.ReaderParseInsitu_DummyHandler_ValidateEncoding_SSE42 (762 ms)
@@ -9 +9 @@
-[       OK ] RapidJson.ReaderParse_DummyHandler_SSE42 (358 ms)
+[       OK ] RapidJson.ReaderParse_DummyHandler_SSE42 (357 ms)
@@ -13 +13 @@
-[       OK ] RapidJson.ReaderParseInsitu_DummyHandler_Booleans_SSE42 (10 ms)
+[       OK ] RapidJson.ReaderParseInsitu_DummyHandler_Booleans_SSE42 (9 ms)
@@ -15 +15 @@
-[       OK ] RapidJson.ReaderParse_DummyHandler_Floats_SSE42 (23 ms)
+[       OK ] RapidJson.ReaderParse_DummyHandler_Floats_SSE42 (24 ms)
@@ -17 +17 @@
-[       OK ] RapidJson.ReaderParseInsitu_DummyHandler_Floats_SSE42 (24 ms)
+[       OK ] RapidJson.ReaderParseInsitu_DummyHandler_Floats_SSE42 (26 ms)
@@ -21 +21 @@
-[       OK ] RapidJson.ReaderParseInsitu_DummyHandler_Guids_SSE42 (24 ms)
+[       OK ] RapidJson.ReaderParseInsitu_DummyHandler_Guids_SSE42 (25 ms)
@@ -23 +23 @@
-[       OK ] RapidJson.ReaderParse_DummyHandler_Integers_SSE42 (17 ms)
+[       OK ] RapidJson.ReaderParse_DummyHandler_Integers_SSE42 (16 ms)
@@ -25 +25 @@
-[       OK ] RapidJson.ReaderParseInsitu_DummyHandler_Integers_SSE42 (17 ms)
+[       OK ] RapidJson.ReaderParseInsitu_DummyHandler_Integers_SSE42 (18 ms)
@@ -27 +27 @@
-[       OK ] RapidJson.ReaderParse_DummyHandler_Mixed_SSE42 (198 ms)
+[       OK ] RapidJson.ReaderParse_DummyHandler_Mixed_SSE42 (223 ms)
@@ -33 +33 @@
-[       OK ] RapidJson.ReaderParseInsitu_DummyHandler_Nulls_SSE42 (8 ms)
+[       OK ] RapidJson.ReaderParseInsitu_DummyHandler_Nulls_SSE42 (9 ms)
@@ -35 +35 @@
-[       OK ] RapidJson.ReaderParse_DummyHandler_Paragraphs_SSE42 (85 ms)
+[       OK ] RapidJson.ReaderParse_DummyHandler_Paragraphs_SSE42 (113 ms)
@@ -37 +37 @@
-[       OK ] RapidJson.ReaderParseInsitu_DummyHandler_Paragraphs_SSE42 (53 ms)
+[       OK ] RapidJson.ReaderParseInsitu_DummyHandler_Paragraphs_SSE42 (56 ms)
@@ -39 +39 @@
-[       OK ] RapidJson.ReaderParse_DummyHandler_FullPrecision_SSE42 (448 ms)
+[       OK ] RapidJson.ReaderParse_DummyHandler_FullPrecision_SSE42 (402 ms)
@@ -41 +41 @@
-[       OK ] RapidJson.ReaderParseIterative_DummyHandler_SSE42 (387 ms)
+[       OK ] RapidJson.ReaderParseIterative_DummyHandler_SSE42 (377 ms)
@@ -43 +43 @@
-[       OK ] RapidJson.ReaderParseIterativeInsitu_DummyHandler_SSE42 (369 ms)
+[       OK ] RapidJson.ReaderParseIterativeInsitu_DummyHandler_SSE42 (371 ms)
@@ -45 +45 @@
-[       OK ] RapidJson.ReaderParseIterativePull_DummyHandler_SSE42 (389 ms)
+[       OK ] RapidJson.ReaderParseIterativePull_DummyHandler_SSE42 (382 ms)
@@ -47 +47 @@
-[       OK ] RapidJson.ReaderParseIterativePullInsitu_DummyHandler_SSE42 (384 ms)
+[       OK ] RapidJson.ReaderParseIterativePullInsitu_DummyHandler_SSE42 (385 ms)
@@ -49 +49 @@
-[       OK ] RapidJson.ReaderParse_DummyHandler_ValidateEncoding_SSE42 (836 ms)
+[       OK ] RapidJson.ReaderParse_DummyHandler_ValidateEncoding_SSE42 (841 ms)
@@ -51 +51 @@
-[       OK ] RapidJson.DocumentParseInsitu_MemoryPoolAllocator_SSE42 (373 ms)
+[       OK ] RapidJson.DocumentParseInsitu_MemoryPoolAllocator_SSE42 (426 ms)
@@ -53 +53 @@
-[       OK ] RapidJson.DocumentParseIterativeInsitu_MemoryPoolAllocator_SSE42 (400 ms)
+[       OK ] RapidJson.DocumentParseIterativeInsitu_MemoryPoolAllocator_SSE42 (446 ms)
@@ -55 +55 @@
-[       OK ] RapidJson.DocumentParse_MemoryPoolAllocator_SSE42 (401 ms)
+[       OK ] RapidJson.DocumentParse_MemoryPoolAllocator_SSE42 (453 ms)
@@ -57 +57 @@
-[       OK ] RapidJson.DocumentParseLength_MemoryPoolAllocator_SSE42 (516 ms)
+[       OK ] RapidJson.DocumentParseLength_MemoryPoolAllocator_SSE42 (564 ms)
@@ -59 +59 @@
-[       OK ] RapidJson.DocumentParseStdString_MemoryPoolAllocator_SSE42 (405 ms)
+[       OK ] RapidJson.DocumentParseStdString_MemoryPoolAllocator_SSE42 (452 ms)
@@ -61 +61 @@
-[       OK ] RapidJson.DocumentParseIterative_MemoryPoolAllocator_SSE42 (420 ms)
+[       OK ] RapidJson.DocumentParseIterative_MemoryPoolAllocator_SSE42 (475 ms)
@@ -63 +63 @@
-[       OK ] RapidJson.DocumentParse_CrtAllocator_SSE42 (563 ms)
+[       OK ] RapidJson.DocumentParse_CrtAllocator_SSE42 (656 ms)
@@ -65 +65 @@
-[       OK ] RapidJson.DocumentParseEncodedInputStream_MemoryStream_SSE42 (520 ms)
+[       OK ] RapidJson.DocumentParseEncodedInputStream_MemoryStream_SSE42 (569 ms)
@@ -67 +67 @@
-[       OK ] RapidJson.DocumentParseAutoUTFInputStream_MemoryStream_SSE42 (2334 ms)
+[       OK ] RapidJson.DocumentParseAutoUTFInputStream_MemoryStream_SSE42 (2217 ms)
@@ -69 +69 @@
-[       OK ] RapidJson.DocumentTraverse (10 ms)
+[       OK ] RapidJson.DocumentTraverse (12 ms)
@@ -71 +71 @@
-[       OK ] RapidJson.DocumentAccept (12 ms)
+[       OK ] RapidJson.DocumentAccept (14 ms)
@@ -73 +73 @@
-[       OK ] RapidJson.DocumentFind (340 ms)
+[       OK ] RapidJson.DocumentFind (45 ms)
@@ -75 +75 @@
-[       OK ] RapidJson.Writer_NullStream (132 ms)
+[       OK ] RapidJson.Writer_NullStream (133 ms)
@@ -77 +77 @@
-[       OK ] RapidJson.Writer_StringBuffer_SSE42 (306 ms)
+[       OK ] RapidJson.Writer_StringBuffer_SSE42 (304 ms)
@@ -79 +79 @@
-[       OK ] RapidJson.Writer_StringBuffer_Booleans_SSE42 (15 ms)
+[       OK ] RapidJson.Writer_StringBuffer_Booleans_SSE42 (16 ms)
@@ -81 +81 @@
-[       OK ] RapidJson.Writer_StringBuffer_Floats_SSE42 (49 ms)
+[       OK ] RapidJson.Writer_StringBuffer_Floats_SSE42 (46 ms)
@@ -83 +83 @@
-[       OK ] RapidJson.Writer_StringBuffer_Guids_SSE42 (32 ms)
+[       OK ] RapidJson.Writer_StringBuffer_Guids_SSE42 (30 ms)
@@ -85 +85 @@
-[       OK ] RapidJson.Writer_StringBuffer_Integers_SSE42 (15 ms)
+[       OK ] RapidJson.Writer_StringBuffer_Integers_SSE42 (16 ms)
@@ -87 +87 @@
-[       OK ] RapidJson.Writer_StringBuffer_Mixed_SSE42 (165 ms)
+[       OK ] RapidJson.Writer_StringBuffer_Mixed_SSE42 (151 ms)
@@ -91 +91 @@
-[       OK ] RapidJson.Writer_StringBuffer_Paragraphs_SSE42 (75 ms)
+[       OK ] RapidJson.Writer_StringBuffer_Paragraphs_SSE42 (73 ms)
@@ -93 +93 @@
-[       OK ] RapidJson.PrettyWriter_StringBuffer_SSE42 (353 ms)
+[       OK ] RapidJson.PrettyWriter_StringBuffer_SSE42 (355 ms)
@@ -97 +97 @@
-[       OK ] RapidJson.SkipWhitespace_Basic (592 ms)
+[       OK ] RapidJson.SkipWhitespace_Basic (434 ms)
@@ -99 +99 @@
-[       OK ] RapidJson.SkipWhitespace_SSE42 (55 ms)
+[       OK ] RapidJson.SkipWhitespace_SSE42 (56 ms)
@@ -101 +101 @@
-[       OK ] RapidJson.SkipWhitespace_strspn (55 ms)
+[       OK ] RapidJson.SkipWhitespace_strspn (56 ms)
@@ -103 +103 @@
-[       OK ] RapidJson.UTF8_Validate (827 ms)
+[       OK ] RapidJson.UTF8_Validate (836 ms)
@@ -105 +105 @@
-[       OK ] RapidJson.FileReadStream (230 ms)
+[       OK ] RapidJson.FileReadStream (228 ms)
@@ -107 +107 @@
-[       OK ] RapidJson.ReaderParse_DummyHandler_FileReadStream_SSE42 (939 ms)
+[       OK ] RapidJson.ReaderParse_DummyHandler_FileReadStream_SSE42 (918 ms)
@@ -109 +109 @@
-[       OK ] RapidJson.IStreamWrapper (235 ms)
+[       OK ] RapidJson.IStreamWrapper (234 ms)
@@ -111 +111 @@
-[       OK ] RapidJson.IStreamWrapper_Unbuffered (2817 ms)
+[       OK ] RapidJson.IStreamWrapper_Unbuffered (2791 ms)
@@ -113 +113 @@
-[       OK ] RapidJson.IStreamWrapper_Setbuffered (2771 ms)
+[       OK ] RapidJson.IStreamWrapper_Setbuffered (2766 ms)
@@ -115 +115 @@
-[       OK ] RapidJson.ReaderParse_DummyHandler_IStreamWrapper_SSE42 (946 ms)
+[       OK ] RapidJson.ReaderParse_DummyHandler_IStreamWrapper_SSE42 (988 ms)
@@ -117 +117 @@
-[       OK ] RapidJson.ReaderParse_DummyHandler_IStreamWrapper_Unbuffered_SSE42 (3310 ms)
+[       OK ] RapidJson.ReaderParse_DummyHandler_IStreamWrapper_Unbuffered_SSE42 (3460 ms)
@@ -119 +119 @@
-[       OK ] RapidJson.ReaderParse_DummyHandler_IStreamWrapper_Setbuffered_SSE42 (3318 ms)
+[       OK ] RapidJson.ReaderParse_DummyHandler_IStreamWrapper_Setbuffered_SSE42 (3378 ms)
@@ -122 +122 @@
-[----------] 59 tests from RapidJson (28531 ms total)
+[----------] 59 tests from RapidJson (28615 ms total)
@@ -126 +126 @@
-100000 trials in 6.566995 s -> 15227.665013 trials per sec
+100000 trials in 7.838233 s -> 12757.977468 trials per sec
@@ -128,2 +128,2 @@
-[       OK ] Schema.TestSuite (6581 ms)
-[----------] 1 test from Schema (6581 ms total)
+[       OK ] Schema.TestSuite (7850 ms)
+[----------] 1 test from Schema (7850 ms total)
@@ -132 +132 @@
-[==========] 60 tests from 2 test cases ran. (35112 ms total)
+[==========] 60 tests from 2 test cases ran. (36465 ms total)

@coveralls
Copy link

coveralls commented Apr 11, 2019

Coverage Status

Coverage increased (+0.1%) to 99.864% when pulling be4a5a9 on ylavic:MemberMap into 7d801bb on Tencent:master.

@ylavic ylavic force-pushed the MemberMap branch 4 times, most recently from 50d0bd8 to 5c454a0 Compare April 12, 2019 00:30
@miloyip
Copy link
Collaborator

miloyip commented Apr 12, 2019

This seems promising but it really needs time to review.

@ylavic
Copy link
Contributor Author

ylavic commented Apr 2, 2021

@miloyip this PR needs 7546eb2 from #1503 to pass (see https://travis-ci.org/github/Tencent/rapidjson/jobs/765633543#L2086 for instance), the allocator cannot be destroyed before the Values using it.

I'll merge PR #1503 here until it's in master..

@ylavic
Copy link
Contributor Author

ylavic commented Apr 2, 2021

Actually the whole PR #1503 is needed (not only 7546eb2, but also c033292), otherwise with -D_GLIBCXX_DEBUG I've got things like:

[ RUN      ] Value.MergeDuplicateKey
/usr/include/c++/10/debug/safe_iterator.h:193:
In function:
    __gnu_debug::_Safe_iterator<_Iterator, _Sequence,
    _Category>::_Safe_iterator(__gnu_debug::_Safe_iterator<_Iterator,
    _Sequence, _Category>&&) [with _Iterator =
    std::_Rb_tree_iterator<std::pair<const
    rapidjson::GenericValue<rapidjson::UTF8<> >::Data, unsigned int> >;
    _Sequence =
    std::__debug::multimap<rapidjson::GenericValue<rapidjson::UTF8<>
    >::Data, unsigned int, rapidjson::GenericValue<rapidjson::UTF8<>
    >::MapTraits::Less, rapidjson::StdAllocator<std::pair<const
    rapidjson::GenericValue<rapidjson::UTF8<> >::Data, unsigned int>,
    rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator> > >; _Category =
    std::forward_iterator_tag]

Error: attempt to copy-construct an iterator from a singular iterator.

Objects involved in the operation:
    iterator "this" @ 0x0x7ffc72652f30 {
      type = std::_Rb_tree_iterator<std::pair<rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator> >::Data const, unsigned int> > (mutable iterator);
      state = singular;
    }
    iterator "other" @ 0x0x560022270a28 {
      type = std::_Rb_tree_iterator<std::pair<rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator> >::Data const, unsigned int> > (mutable iterator);
      state = singular;
    }
Aborted   

Currently GenericValue& operator=(GenericValue& rhs) is unsafe when rhs is an inner Element or a Member of the lhs, lhs cannot be destroyed first.

@ylavic ylavic changed the title Member map Object members stored in std::multimap Apr 2, 2021
@ylavic ylavic force-pushed the MemberMap branch 4 times, most recently from c090ee8 to a2343c6 Compare April 3, 2021 21:49
…ers.

When RAPIDJSON_USE_MEMBERSMAP is defined, an object Value will store
its members in an (re)allocated array of Members like before, but also
in an std::multimap<GenericValue::Data,SizeType> where the key and value
reference the corresponding Member by its Data and index in the array,
respectively, and in a relocatable manner.

The layout of the members map/array is now:
 {multimap*}<>{capacity}<>{Member[capacity]}<>{multimap::iterator[capacity]}
where <> stands for the RAPIDJSON_ALIGN-ment of each part, if needed.

This layout needs to be reallocated when the current capacity is
exhausted, which requires to take care of the multimap and its iterators
explicitely. The multimap is allocated separately and only its pointer is
saved in this layout, so it can easily be restored in its new position.
As for the old/alive iterators, they must move to their new offset according
to the new capacity.

With this in place, it's immediate to get the multimap::iterator from a
MemberIterator and vice versa, thus the same complexity applies for the
operations with MemberIterator or MapIterator.

For FindMember() and RemoveMember(), the complexity drops from O(n) to
the multimap/rbtree's O(log n).
For EraseMember() it drops from O(n-m) to O((log n)-m), m representing
the move/copy of the trailing members.
For AddMember() though, the complexity grows from O(1) to O(log n) due to
the insertion in the multimap too.

Consequently parsing will be slower, up to ~20% measured in perftests on
my laptop (since it's mainly composed of insertions). But later work on
the Document (usually the goal of parsing...) will be much faster; the
new DocumentFind perftest included in this commit is 8 times faster with
RAPIDJSON_USE_MEMBERSMAP (still on my laptop). Overall the tests are 4%
slower (mainly composed of parsing), and notably 15% slower for schemas
parsing/validation (which supposedly comes from the larger JSON files
parsing, still). As a side note, when RAPIDJSON_USE_MEMBERSMAP is not
defined, this commit does nothing (same results for perftest with regard
to previous versions).

Finally, the multimap is allocated and constructed using StdAllocator,
so they will use the same Allocator than for any other Value allocation,
and thus will benefit from the same performance/safety/security/whatever
provided by the user given Allocator.
@miloyip miloyip merged commit 47b837e into Tencent:master Apr 8, 2021
@ylavic ylavic deleted the MemberMap branch April 9, 2021 11:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants