Compare commits

..
Author SHA1 Message Date
Niels Lohmann 1a2828f36c Iterator: test for objects before arrays
#5835 replaced the switch on the value type in operator*, operator->,
operator++, operator-- and operator== of iter_impl with an if chain that
tests for an array first. Every step over an object then pays for a
failed test and a taken branch: with the iterator inlined, walking
twitter.json took 14% more cycles than with 3.12.0, and citm_catalog.json
3.5% more.

Test for objects first. Walking every value of the three files now
takes fewer cycles than with 3.12.0 (Apple M1 Max, Apple clang 21):
citm_catalog.json 1.21x, twitter.json 1.04x, canada.json 1.17x as fast.
canada.json, which holds only arrays, gives back part of the gain of
#5835 (develop: 1.62x); the other orders tried (arrays first without
hints, objects first without hints, a switch) each left one of the
files slower than 3.12.0.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-07 23:44:11 +01:00
5 changed files with 66 additions and 251 deletions

No files matched your search

@@ -1348,64 +1348,6 @@ struct iterator_input_adapter_factory<IteratorType, SentinelType, enable_if_t<is
} }
}; };
/*!
@brief whether @a IteratorType is an iterator of std::string or of a
std::vector of 1-byte elements
Their elements are contiguous, but before C++20 that cannot be detected from
the iterator type (std::contiguous_iterator), so an iterator pair of them would
be read one character at a time through the iterators. They are read as the
pointer range they are instead, as the containers themselves are (see the
container overload of input_adapter()). The types are matched exactly, so no
other iterator can be mistaken for one. (std::basic_string is only matched for
char: other character types need std::char_traits specializations that the
standard library need not have.)
*/
template<typename CharType>
struct is_byte_char_type
{
// a member, not an integral_constant argument, which astyle misformats
static constexpr bool value = sizeof(CharType) == 1 && std::is_integral<CharType>::value && !std::is_same<CharType, bool>::value;
};
template<typename IteratorType, typename CharType, bool IsByte = is_byte_char_type<CharType>::value>
struct is_std_contiguous_byte_iterator_impl : std::false_type {};
// (the containers are only named for byte types)
template<typename IteratorType, typename CharType>
struct is_std_contiguous_byte_iterator_impl<IteratorType, CharType, true>
{
static constexpr bool value = !std::is_pointer<IteratorType>::value
&& (std::is_same<IteratorType, typename std::vector<CharType>::iterator>::value
|| std::is_same<IteratorType, typename std::vector<CharType>::const_iterator>::value
|| std::is_same<IteratorType, std::string::iterator>::value
|| std::is_same<IteratorType, std::string::const_iterator>::value);
};
template<typename IteratorType>
struct is_std_contiguous_byte_iterator
: is_std_contiguous_byte_iterator_impl<IteratorType, typename std::remove_cv<typename std::iterator_traits<IteratorType>::value_type>::type> {};
template<typename IteratorType>
struct iterator_input_adapter_factory<IteratorType, IteratorType, enable_if_t<is_std_contiguous_byte_iterator<IteratorType>::value>>
{
using iterator_type = IteratorType;
using sentinel_type = IteratorType;
using char_type = typename std::remove_cv<typename std::iterator_traits<iterator_type>::value_type>::type;
using adapter_type = iterator_input_adapter<const char_type*>;
static adapter_type create(IteratorType first, IteratorType last)
{
if (first == last)
{
// (an empty range must not be dereferenced)
return adapter_type(nullptr, nullptr);
}
const char_type* const begin = std::addressof(*first);
return adapter_type(begin, begin + (last - first));
}
};
// General purpose iterator-based input (iterator+sentinel pair; SentinelType // General purpose iterator-based input (iterator+sentinel pair; SentinelType
// defaults to IteratorType for the common same-type case, but may differ for // defaults to IteratorType for the common same-type case, but may differ for
// C++20 ranges-style iterator+sentinel pairs). Only enable for types that can // C++20 ranges-style iterator+sentinel pairs). Only enable for types that can
+33 -29
View File
@@ -277,18 +277,22 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{ {
JSON_ASSERT(m_object != nullptr); JSON_ASSERT(m_object != nullptr);
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array)) // objects are tested before arrays here and in the other operators:
{ // with arrays first, walking a document of objects such as
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end()); // twitter.json took 14% more cycles than with 3.12.0's switch; with
return *m_it.array_iterator; // objects first, no document walks slower than with 3.12.0
}
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object)) if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
{ {
JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end()); JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());
return m_it.object_iterator->second; return m_it.object_iterator->second;
} }
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
{
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
return *m_it.array_iterator;
}
// neither array nor object: a primitive value (null has no value to point to) // neither array nor object: a primitive value (null has no value to point to)
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type != value_t::null && m_it.primitive_iterator.is_begin())) if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type != value_t::null && m_it.primitive_iterator.is_begin()))
{ {
@@ -306,18 +310,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{ {
JSON_ASSERT(m_object != nullptr); JSON_ASSERT(m_object != nullptr);
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
{
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
return &*m_it.array_iterator;
}
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object)) if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
{ {
JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end()); JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());
return &(m_it.object_iterator->second); return &(m_it.object_iterator->second);
} }
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
{
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
return &*m_it.array_iterator;
}
// neither array nor object: a primitive value // neither array nor object: a primitive value
if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin()))
{ {
@@ -346,18 +350,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{ {
JSON_ASSERT(m_object != nullptr); JSON_ASSERT(m_object != nullptr);
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
{
++m_it.array_iterator;
return *this;
}
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object)) if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
{ {
std::advance(m_it.object_iterator, 1); std::advance(m_it.object_iterator, 1);
return *this; return *this;
} }
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
{
++m_it.array_iterator;
return *this;
}
// neither array nor object: a primitive value // neither array nor object: a primitive value
++m_it.primitive_iterator; ++m_it.primitive_iterator;
return *this; return *this;
@@ -382,18 +386,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{ {
JSON_ASSERT(m_object != nullptr); JSON_ASSERT(m_object != nullptr);
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
{
--m_it.array_iterator;
return *this;
}
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object)) if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
{ {
std::advance(m_it.object_iterator, -1); std::advance(m_it.object_iterator, -1);
return *this; return *this;
} }
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
{
--m_it.array_iterator;
return *this;
}
// neither array nor object: a primitive value // neither array nor object: a primitive value
--m_it.primitive_iterator; --m_it.primitive_iterator;
return *this; return *this;
@@ -415,16 +419,16 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
return true; return true;
} }
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
{
return (m_it.array_iterator == other.m_it.array_iterator);
}
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object)) if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
{ {
return (m_it.object_iterator == other.m_it.object_iterator); return (m_it.object_iterator == other.m_it.object_iterator);
} }
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
{
return (m_it.array_iterator == other.m_it.array_iterator);
}
// neither array nor object: a primitive value // neither array nor object: a primitive value
return (m_it.primitive_iterator == other.m_it.primitive_iterator); return (m_it.primitive_iterator == other.m_it.primitive_iterator);
} }
+33 -87
View File
@@ -10457,64 +10457,6 @@ struct iterator_input_adapter_factory<IteratorType, SentinelType, enable_if_t<is
} }
}; };
/*!
@brief whether @a IteratorType is an iterator of std::string or of a
std::vector of 1-byte elements
Their elements are contiguous, but before C++20 that cannot be detected from
the iterator type (std::contiguous_iterator), so an iterator pair of them would
be read one character at a time through the iterators. They are read as the
pointer range they are instead, as the containers themselves are (see the
container overload of input_adapter()). The types are matched exactly, so no
other iterator can be mistaken for one. (std::basic_string is only matched for
char: other character types need std::char_traits specializations that the
standard library need not have.)
*/
template<typename CharType>
struct is_byte_char_type
{
// a member, not an integral_constant argument, which astyle misformats
static constexpr bool value = sizeof(CharType) == 1 && std::is_integral<CharType>::value && !std::is_same<CharType, bool>::value;
};
template<typename IteratorType, typename CharType, bool IsByte = is_byte_char_type<CharType>::value>
struct is_std_contiguous_byte_iterator_impl : std::false_type {};
// (the containers are only named for byte types)
template<typename IteratorType, typename CharType>
struct is_std_contiguous_byte_iterator_impl<IteratorType, CharType, true>
{
static constexpr bool value = !std::is_pointer<IteratorType>::value
&& (std::is_same<IteratorType, typename std::vector<CharType>::iterator>::value
|| std::is_same<IteratorType, typename std::vector<CharType>::const_iterator>::value
|| std::is_same<IteratorType, std::string::iterator>::value
|| std::is_same<IteratorType, std::string::const_iterator>::value);
};
template<typename IteratorType>
struct is_std_contiguous_byte_iterator
: is_std_contiguous_byte_iterator_impl<IteratorType, typename std::remove_cv<typename std::iterator_traits<IteratorType>::value_type>::type> {};
template<typename IteratorType>
struct iterator_input_adapter_factory<IteratorType, IteratorType, enable_if_t<is_std_contiguous_byte_iterator<IteratorType>::value>>
{
using iterator_type = IteratorType;
using sentinel_type = IteratorType;
using char_type = typename std::remove_cv<typename std::iterator_traits<iterator_type>::value_type>::type;
using adapter_type = iterator_input_adapter<const char_type*>;
static adapter_type create(IteratorType first, IteratorType last)
{
if (first == last)
{
// (an empty range must not be dereferenced)
return adapter_type(nullptr, nullptr);
}
const char_type* const begin = std::addressof(*first);
return adapter_type(begin, begin + (last - first));
}
};
// General purpose iterator-based input (iterator+sentinel pair; SentinelType // General purpose iterator-based input (iterator+sentinel pair; SentinelType
// defaults to IteratorType for the common same-type case, but may differ for // defaults to IteratorType for the common same-type case, but may differ for
// C++20 ranges-style iterator+sentinel pairs). Only enable for types that can // C++20 ranges-style iterator+sentinel pairs). Only enable for types that can
@@ -24564,18 +24506,22 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{ {
JSON_ASSERT(m_object != nullptr); JSON_ASSERT(m_object != nullptr);
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array)) // objects are tested before arrays here and in the other operators:
{ // with arrays first, walking a document of objects such as
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end()); // twitter.json took 14% more cycles than with 3.12.0's switch; with
return *m_it.array_iterator; // objects first, no document walks slower than with 3.12.0
}
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object)) if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
{ {
JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end()); JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());
return m_it.object_iterator->second; return m_it.object_iterator->second;
} }
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
{
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
return *m_it.array_iterator;
}
// neither array nor object: a primitive value (null has no value to point to) // neither array nor object: a primitive value (null has no value to point to)
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type != value_t::null && m_it.primitive_iterator.is_begin())) if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type != value_t::null && m_it.primitive_iterator.is_begin()))
{ {
@@ -24593,18 +24539,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{ {
JSON_ASSERT(m_object != nullptr); JSON_ASSERT(m_object != nullptr);
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
{
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
return &*m_it.array_iterator;
}
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object)) if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
{ {
JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end()); JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());
return &(m_it.object_iterator->second); return &(m_it.object_iterator->second);
} }
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
{
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
return &*m_it.array_iterator;
}
// neither array nor object: a primitive value // neither array nor object: a primitive value
if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin()))
{ {
@@ -24633,18 +24579,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{ {
JSON_ASSERT(m_object != nullptr); JSON_ASSERT(m_object != nullptr);
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
{
++m_it.array_iterator;
return *this;
}
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object)) if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
{ {
std::advance(m_it.object_iterator, 1); std::advance(m_it.object_iterator, 1);
return *this; return *this;
} }
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
{
++m_it.array_iterator;
return *this;
}
// neither array nor object: a primitive value // neither array nor object: a primitive value
++m_it.primitive_iterator; ++m_it.primitive_iterator;
return *this; return *this;
@@ -24669,18 +24615,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{ {
JSON_ASSERT(m_object != nullptr); JSON_ASSERT(m_object != nullptr);
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
{
--m_it.array_iterator;
return *this;
}
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object)) if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
{ {
std::advance(m_it.object_iterator, -1); std::advance(m_it.object_iterator, -1);
return *this; return *this;
} }
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
{
--m_it.array_iterator;
return *this;
}
// neither array nor object: a primitive value // neither array nor object: a primitive value
--m_it.primitive_iterator; --m_it.primitive_iterator;
return *this; return *this;
@@ -24702,16 +24648,16 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
return true; return true;
} }
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
{
return (m_it.array_iterator == other.m_it.array_iterator);
}
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object)) if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
{ {
return (m_it.object_iterator == other.m_it.object_iterator); return (m_it.object_iterator == other.m_it.object_iterator);
} }
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
{
return (m_it.array_iterator == other.m_it.array_iterator);
}
// neither array nor object: a primitive value // neither array nor object: a primitive value
return (m_it.primitive_iterator == other.m_it.primitive_iterator); return (m_it.primitive_iterator == other.m_it.primitive_iterator);
} }
-2
View File
@@ -1674,8 +1674,6 @@ TEST_CASE("BSON input that cannot be read is discarded by every overload")
json _; json _;
CHECK_THROWS_AS(_ = json::from_bson(input.begin(), input.end()), json::parse_error&); CHECK_THROWS_AS(_ = json::from_bson(input.begin(), input.end()), json::parse_error&);
CHECK(json::from_bson(input, true, false).is_discarded()); CHECK(json::from_bson(input, true, false).is_discarded());
// false positive: Infer takes std::integral_constant<bool, false>::value for a pointer
// @infer-ignore NULLPTR_DEREFERENCE
CHECK(json::from_bson(input.begin(), input.end(), true, false).is_discarded()); CHECK(json::from_bson(input.begin(), input.end(), true, false).is_discarded());
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED #ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
CHECK(json::from_bson(input.data(), input.size(), true, false).is_discarded()); CHECK(json::from_bson(input.data(), input.size(), true, false).is_discarded());
-75
View File
@@ -32,7 +32,6 @@ using nlohmann::json;
#include <iostream> #include <iostream>
#include <iterator> #include <iterator>
#include <list>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <valarray> #include <valarray>
@@ -1410,77 +1409,3 @@ TEST_CASE("indentation runs of every length around the 8-byte skip")
CHECK(parse_result(indented("[\n~1]", std::string(17, ' ')), true) == "[1]"); CHECK(parse_result(indented("[\n~1]", std::string(17, ' ')), true) == "[1]");
} }
#endif #endif
#if !defined(JSON_NOEXCEPTION)
namespace
{
/// the value of parsing [first, last), or the message of the exception
template<typename Iterator>
std::string outcome_of(Iterator first, Iterator last)
{
try
{
return json::parse(first, last).dump();
}
catch (const json::exception& e)
{
return e.what();
}
}
/// the bytes of @a text as a vector of @a T (explicit casts: no implicit char conversion)
template<typename T>
std::vector<T> bytes_as(const std::string& text)
{
std::vector<T> result;
result.reserve(text.size());
for (const char c : text)
{
result.push_back(static_cast<T>(c));
}
return result;
}
/// a std::string, std::vector<char>, <unsigned char> and <signed char> as
/// iterator pairs: the same as the byte path (std::list iterators)
void check_iterator_pairs(const std::string& text)
{
CAPTURE(text)
const std::list<char> bytes(text.begin(), text.end());
const std::string expected = outcome_of(bytes.begin(), bytes.end());
const bool expected_accept = json::accept(bytes.begin(), bytes.end());
std::string s = text;
CHECK(outcome_of(s.begin(), s.end()) == expected);
CHECK(outcome_of(s.cbegin(), s.cend()) == expected);
CHECK(json::accept(s.cbegin(), s.cend()) == expected_accept);
const std::vector<char> vc(text.begin(), text.end());
CHECK(outcome_of(vc.begin(), vc.end()) == expected);
const std::vector<unsigned char> vu = bytes_as<unsigned char>(text);
CHECK(outcome_of(vu.begin(), vu.end()) == expected);
CHECK(json::accept(vu.begin(), vu.end()) == expected_accept);
const std::vector<signed char> vs = bytes_as<signed char>(text);
CHECK(outcome_of(vs.begin(), vs.end()) == expected);
}
} // namespace
TEST_CASE("deserialization from iterator pairs of std::string and std::vector")
{
// these are read as the pointer range they are, also before C++20
for (const char* text :
{
"", " ", R"([1, 2.5, "a", true, null, {"k": ["v"]}])", "[1, 2", R"({"a" 1})", "\"\xC3\xA4\"", "\"\xC3\"", "[1]x", "1e400"
})
{
check_iterator_pairs(text);
}
check_iterator_pairs(std::string("[1]" "\0" "x", 5));
// an empty range is not dereferenced
std::string empty;
CHECK_FALSE(json::accept(empty.begin(), empty.end()));
json _;
CHECK_THROWS_AS(_ = json::parse(empty.begin(), empty.end()), json::parse_error&);
}
#endif