Compose the C++ XML de-serialization - #745
Merged
Merged
Conversation
C++ is the last backend to have its XML read side decomposed, after C# (#686), Go (#688), Java (#690), Python (#694) and TypeScript (#698). #673 factored out the framing of an *element*; this factors out the framing of the property *loop*, which every ``<Cls>FromSequence`` re-emitted in full. **A property** loses its duplicate check and its ``std::tie``, and names only itself and what reads it. // before case properties::OfExtension::kSemanticId: { if (the_semantic_id.has_value()) { error = DuplicatePropertyError(name); break; } std::tie( the_semantic_id, error ) = ReferenceFromSequence< types::IReference >(reader); break; } // after case properties::OfExtension::kSemanticId: return ReadInto( the_semantic_id, ReferenceFromSequence< types::IReference >(reader) ); **The loop** around it becomes one call. It could be lifted out at all only because the framing now returns the error alone, instead of ``NoInstanceAndDeserializationError<std::shared_ptr<T> >``, and so no longer needs ``T``. // before, ~220 lines in each of the 38 ``<Cls>FromSequence`` while (true) { error = SkipWhitespace(reader); if (error.has_value()) { ...6 lines... } if (reader.node().kind() == xml_common::NodeKind::Stop) { break; } else if (reader.node().kind() != xml_common::NodeKind::Start) { ...10 lines naming IExtension... } const std::string name(...); reader.Read(); auto it = properties::kMapOfExtension.find(name); if (it == properties::kMapOfExtension.end()) { ...12 lines... } switch (it->second) { ... } ...~60 lines of prepending and stop-element handling... } // after common::optional<DeserializationError> error( ReadProperties< properties::kPropertyCountOfExtension >( reader, properties::kMapOfExtension, L"IExtension", [&]( properties::OfExtension property ) -> common::optional<DeserializationError> { switch (property) { ... } } ) ); The ``switch`` stays inline because its branches assign the locals the constructor is called with. The lambda is a template argument taken by ``const&``, so the call is statically bound and nothing is type-erased or allocated; ``g++ -O2`` keeps ``ReadProperties`` itself out of line, one instantiation per class, which is as many function bodies as the loops it replaces. **The duplicate property element check** is bookkeeping of the loop, not of the property, so it moved there, as a bit set sized to the class. It still precedes the read, so a duplicate is refused without its content ever being looked at. std::bitset<kPropertyCount> seen; ... if (seen[index]) { error = DuplicatePropertyError(name); PrependElementSegmentToDeserializationError(name, *error); return error; } seen[index] = true; **``ReadInto`` takes the results of a read, not the reader.** A tuple's item readers vary in number *and* in type, so no reader-taking signature serves them all, while one taking the result serves every read. template <typename T> common::optional<DeserializationError> ReadInto( common::optional<T>& target, std::pair< common::optional<T>, common::optional<DeserializationError> >&& read ); **A named union is framed like a class.** The two framings were ~160 near-identical lines differing only in the value type, which every error factory they call is already generic in. // before template <typename T, typename DispatchT> ... DeserializeClassFromElement(reader, const std::wstring&, dispatch); template <typename VariantT, typename DispatchT> ... DeserializeUnionFromElement(reader, const std::wstring&, dispatch); // after template <typename ValueT, typename DispatchT> ... DeserializeFromElement(reader, const wchar_t*, dispatch); **An element with a sole model type does not dispatch.** 40 of the 51 ``<Cls>FromElement`` spelled out a one-case ``switch`` inside a fifteen-line lambda signature; the other 11 keep theirs. return DeserializeSoleFromElement< std::shared_ptr<types::IExtension> >( reader, L"IExtension", types::ModelType::kExtension, ExtensionFromSequence<types::IExtension> ); **The public ``<Cls>From``** were 51 copies of the same 54 lines: open the reader, read the root element, check that nothing but whitespace follows. return DeserializeFrom< std::shared_ptr<types::IExtension> >( is, options, ExtensionFromElement ); **``interface_name`` is a ``const wchar_t*``**, not a ``const std::wstring&``: every call site used to construct a ``std::wstring`` on *every* element read, success path included, for a message which is almost never built. ``xmlization.cpp`` for the AAS meta-model goes 34,543 -> 24,634 lines and its read region 23,584 -> 13,675 (-42%).
Coverage Report for CI Build 35737536379Coverage increased (+0.005%) to 87.176%Details
Uncovered ChangesNo uncovered changes found. Coverage Regressions42 previously-covered lines in 1 file lost coverage.
Coverage Stats
💛 - Coveralls |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
C++ is the last backend to have its XML read side decomposed, after C# (#686), Go (#688), Java (#690), Python (#694) and TypeScript (#698). #673 factored out the framing of an element; this factors out the framing of the property loop, which every
<Cls>FromSequencere-emitted in full.A property loses its duplicate check and its
std::tie, and names only itself and what reads it.The loop around it becomes one call. It could be lifted out at all only because the framing now returns the error alone, instead of
NoInstanceAndDeserializationError<std::shared_ptr<T> >, and so no longer needsT.The
switchstays inline because its branches assign the locals the constructor is called with. The lambda is a template argument taken byconst&, so the call is statically bound and nothing is type-erased or allocated;g++ -O2keepsReadPropertiesitself out of line, one instantiation per class, which is as many function bodies as the loops it replaces.The duplicate property element check is bookkeeping of the loop, not of the property, so it moved there, as a bit set sized to the class. It still precedes the read, so a duplicate is refused without its content ever being looked at.
ReadIntotakes the results of a read, not the reader. A tuple's item readers vary in number and in type, so no reader-taking signature serves them all, while one taking the result serves every read.A named union is framed like a class. The two framings were ~160 near-identical lines differing only in the value type, which every error factory they call is already generic in.
An element with a sole model type does not dispatch. 40 of the 51
<Cls>FromElementspelled out a one-caseswitchinside a fifteen-line lambda signature; the other 11 keep theirs.The public
<Cls>Fromwere 51 copies of the same 54 lines: open the reader, read the root element, check that nothing but whitespace follows.interface_nameis aconst wchar_t*, not aconst std::wstring&: every call site used to construct astd::wstringon every element read, success path included, for a message which is almost never built.xmlization.cppfor the AAS meta-model goes 34,543 -> 24,634 lines and its read region 23,584 -> 13,675 (-42%).