-
-
Notifications
You must be signed in to change notification settings - Fork 6.8k
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
Optimize copy / assignment and size of json iterator #3452
Optimize copy / assignment and size of json iterator #3452
Conversation
Thanks! It seems @falbrechtskirchinger is working in #3446 on similar issues - can you please check whether his adjustments go in the same direction? |
Some of this is probably redundant due to my pending PR. Will look at the code in a second.
MSVC still can't handle it: But I agree that pessimizing this for everyone else is bad practice.
This project uses Artistic Style to format code which is executed by |
Thanks for your comment. I checked the PR and it's not exactly the same thing, it's more about preparing for C++20 support with ranges. This PR is mainly an optimization of the |
Thanks for taking a look at my PR! I am currently working on a cleaner |
20ce876
to
a9b7161
Compare
: std::alignment_of<ObjectIterator>::value); | ||
|
||
protected: | ||
typename std::aligned_storage<kMaxIteratorSize, kMaxIteratorAlignment>::type m_data; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use alignas(kMaxIteratorAlignment) std::uint8_t m_data[kMaxIteratorSize];
std::aligned_storage
has been deprecated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am working on this PR on my fork. Besides Windows weird issues, this change triggers some warnings (as error):
/__w/json/json/include/nlohmann/detail/iterators/internal_iterator.hpp:67:48: error: do not declare C-style arrays, use std::array<> instead [cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,-warnings-as-errors]
Looks like a bit too much to use std::array
here IMO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can annotate the line with
// NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
to suppress the error.
NOLINTNEXTLINE
works too, if you place it above the statement and not on the same line (probably better here).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR is ready for review (Microsoft issues fixed ;)), although I still have different formatter output with make amalgamate
(my astyle
version is Artistic Style Version 3.1
). Is it an issue, and if so, why do I have all these differences ? (see include/nlohmann/json.hpp
for instance)
a9b7161
to
9878d78
Compare
9878d78
to
32100a0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some initial thoughts.
The formatting of json.hpp
is very distracting. I scrolled through the file very quickly and probably missed most of your changes.
internal_iterator_impl(const internal_iterator_impl& o) noexcept | ||
: m_it_type(o.m_it_type) | ||
{ | ||
switch (m_it_type) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any particular reason you're not using an enum class[ : std::uint8_t]
here?
::new (&this->primitive_iterator()) primitive_iterator_t(o.primitive_iterator()); | ||
break; | ||
default: | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should at least be:
default: // LCOV_EXCL_LINE
JSON_ASSERT(false); // LCOV_EXCL_LINE
Possibly even:
default: // LCOV_EXCL_LINE
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
::new (&this->primitive_iterator()) primitive_iterator_t(std::move(o.primitive_iterator())); | ||
break; | ||
default: | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
this->primitive_iterator() = o.primitive_iterator(); | ||
break; | ||
default: | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And here. You get the idea...
#if !defined(__clang__) && defined(__GNUC__) && (__GNUC__ * 10000) < 50000 | ||
#undef JSON_HAS_TRIVIALLY_COPYABLE | ||
#endif | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a) For it to be overridable, you should define it to 0 or 1, just like the other JSON_HAS_*
macros.
b) Use the Hedley version check macro: JSON_HEDLEY_GNUC_VERSION_CHECK(5, 0, 0)
.
c) (For later:) Remember to document it in docs/mkdocs/docs/api/macros/index.md
+ docs/mkdocs/docs/api/macros/json_has_trivially_copyable.md
.
include/nlohmann/json.hpp
Outdated
#include <numeric> // accumulate | ||
#include <string> // string, stoi, to_string | ||
#include <utility> // declval, forward, move, pair, swap | ||
#include <vector> // vector |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
About the formatting: I assume you reformatted with clang-format
and then used astyle
?
astyle
does not produce consistent output for equal source input. It keeps some of the input formatting, especially some white space as you can see here.
I think you'll have to find a way to undo that formatting. Otherwise reviewing and resolving merge conflicts isn't going to be much fun.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"astyle does not produce consistent output for equal source input." Now I understood the issue, yes I have clang-format on save by default in my IDE. I did not know it would harm astyle
format afterwards. I will fix all the formatting issues manually, sorry for the inconvenience.
include/nlohmann/json.hpp
Outdated
|
||
// This could have been written as: | ||
// result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); | ||
// result.m_it.set_array_iterator(m_value.array->insert(pos.m_it.array_iterator(), cnt, val)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've come across a related Codacy issue today ... both things could be solved with a helper. Doesn't have to be here though.
: sizeof(object_iterator_t))]; | ||
#endif | ||
} m_data {}; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Horrible, indeed. I think I have a better solution. Will get back to you when I have more time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should work:
https://godbolt.org/z/MGhhEsd3G
namespace detail
{
template<std::size_t... Is>
struct max;
template<>
struct max<> {
using type = std::integral_constant<std::size_t, 0>;
};
template<std::size_t I, std::size_t... Rest>
struct max<I, Rest...> {
using rest_type = typename max<Rest...>::type;
using type = typename std::conditional<
(I > rest_type::value),
std::integral_constant<std::size_t, I>,
rest_type>::type;
};
template<typename... Ts>
using max_alignof = typename max<alignof(Ts)...>::type;
template<typename... Ts>
using max_sizeof = typename max<sizeof(Ts)...>::type;
}
constexpr std::size_t data_align =
detail::max_alignof<int, double, char>::value;
constexpr std::size_t data_size =
detail::max_sizeof<int, double, char>::value;
alignas(data_align) std::uint8_t data[data_size];
Maybe add a new file meta/algorithm.hpp
or similar.
Edit: On second thought, type_traits.hpp
should be fine.
fa038d1
to
09849a6
Compare
Please update to the latest develop branch (we renamed some folders in #3462). |
09849a6
to
49badfc
Compare
Ok, done |
|
||
namespace nlohmann | ||
{ | ||
namespace detail | ||
{ | ||
|
||
template<class ObjectIterator, class ArrayIterator> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add some documentation on the idea behind the class.
{ | ||
internal_data_t() noexcept = default; | ||
|
||
#ifdef JSON_HAS_CPP_14 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please document the idea behind this piece of code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And a note about the std::aligned_storage
deprecation in C++23.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd still prefer something like my proposed max_alignof
and max_sizeof
traits over nested tertiary operators and separate code paths for C++11 and C++14 and up.
*/ | ||
template<class ObjectIterator, class ArrayIterator> | ||
using internal_iterator = internal_iterator_impl < ObjectIterator, ArrayIterator, | ||
#if defined(__clang__) || !defined(JSON_HEDLEY_GNUC_VERSION) || JSON_HEDLEY_GNUC_VERSION_CHECK(5, 0, 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This condition isn't needed anymore, right? It's handled in your type trait implementation.
@@ -570,5 +570,100 @@ T conditional_static_cast(U value) | |||
return value; | |||
} | |||
|
|||
#if defined(__clang__) || !defined(JSON_HEDLEY_GNUC_VERSION) || JSON_HEDLEY_GNUC_VERSION_CHECK(5, 0, 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be simplified to:
#if !defined(JSON_HEDLEY_GNUC_VERSION) || JSON_HEDLEY_GNUC_VERSION_CHECK(5, 0, 0)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please address @falbrechtskirchinger 's comments.
49badfc
to
a3e6e26
Compare
Hello,
I stambled accross this clang-tidy warning in my project and investigated further,
it
being ajson::const_iterator
:It looks like
json::iterator
is actually non trivially copyable (and quite big), which is unexpected for an iterator and the reason why clang-tidy is complaining. In templated code it is common practice to pass iterators by value and not by reference. After investigation and reading of the code I realized that it was made like that at the beginning as a workaround for a MSVC bug (see #1608). It is sad to impact all other compilation modes and compilers just for a MSVC bug.I redesigned
internal_iterator_t
with two distinct implementations, one optimized in the caseprimitive_iterator_t
,object_t::iterator
andarray_t::iterator
are all trivially copyable (standard case, mainly for non-MSVC compilers), one for the general case (mainly used by MSVC, and other compilers with exotic vector and/or map iterator implementations).This is the fix that I propose, in the hope that since then, the MSVC issue is not a concern anymore. After this PR, all operations on iterators are expected to be faster and iterators can be passed by value without any performance impact like standard operators.