mirror of
https://github.com/nlohmann/json.git
synced 2026-10-22 22:08:55 +00:00
Compare short keys of a large object without a call of memcmp
find_indexed() compared the key of a member the table points to with memcmp, a library call for any length. Up to 16 bytes, the bytes are now compared as two overlapping words (the first and the last 8, 4, or 2 bytes), as find_member() already does for the objects without a table; longer keys still use memcmp. Looking up every key of an object of 1000 random keys, Apple M1 instructions per 1000 lookups (with the previous commit): keys of 5 bytes 93 K -> 72 K, hex strings of 8 bytes 168 K -> 148 K, keys of 12 bytes 186 K -> 170 K, keys of 24 bytes 198 K -> 193 K (unchanged, memcmp). Linux x86-64 Ir (whole run), GCC 13 / Clang 18: 5-byte keys -7.2 % / -7.3 %, 12-byte keys -5.9 % / -6.6 %. Belongs to the PR "json_view: read-side speedups". Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
3 files changed
+181
No files matched your search
@@ -94,6 +94,49 @@ inline std::uint64_t key_hash(const char* s, std::size_t n) noexcept
|
||||
return h ^ (h >> 31u);
|
||||
}
|
||||
|
||||
/// whether the n <= 16 bytes at a and b are equal, as two overlapping words
|
||||
/// (the first and the last 8, 4, or 2 bytes) and without a call
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE bool same_short_bytes(const unsigned char* a, const unsigned char* b, std::size_t n) noexcept
|
||||
{
|
||||
if (n >= 8)
|
||||
{
|
||||
std::uint64_t x0 = 0;
|
||||
std::uint64_t x1 = 0;
|
||||
std::uint64_t y0 = 0;
|
||||
std::uint64_t y1 = 0;
|
||||
std::memcpy(&x0, a, 8);
|
||||
std::memcpy(&x1, a + n - 8, 8);
|
||||
std::memcpy(&y0, b, 8);
|
||||
std::memcpy(&y1, b + n - 8, 8);
|
||||
return x0 == y0 && x1 == y1;
|
||||
}
|
||||
if (n >= 4)
|
||||
{
|
||||
std::uint32_t x0 = 0;
|
||||
std::uint32_t x1 = 0;
|
||||
std::uint32_t y0 = 0;
|
||||
std::uint32_t y1 = 0;
|
||||
std::memcpy(&x0, a, 4);
|
||||
std::memcpy(&x1, a + n - 4, 4);
|
||||
std::memcpy(&y0, b, 4);
|
||||
std::memcpy(&y1, b + n - 4, 4);
|
||||
return x0 == y0 && x1 == y1;
|
||||
}
|
||||
if (n >= 2)
|
||||
{
|
||||
std::uint16_t x0 = 0;
|
||||
std::uint16_t x1 = 0;
|
||||
std::uint16_t y0 = 0;
|
||||
std::uint16_t y1 = 0;
|
||||
std::memcpy(&x0, a, 2);
|
||||
std::memcpy(&x1, a + n - 2, 2);
|
||||
std::memcpy(&y0, b, 2);
|
||||
std::memcpy(&y1, b + n - 2, 2);
|
||||
return x0 == y0 && x1 == y1;
|
||||
}
|
||||
return n == 0 || *a == *b;
|
||||
}
|
||||
|
||||
/// build the table of a large object
|
||||
inline void build_object_index(document_data& d, node* obj)
|
||||
{
|
||||
@@ -158,6 +201,27 @@ inline const node* find_indexed(const document_data& d, const node* obj, const c
|
||||
const document_data::object_index& ix = d.indexes[obj->extra - 1u];
|
||||
const std::uint32_t* const table = d.index_slots.data() + ix.start;
|
||||
std::size_t slot = conditional_static_cast<std::size_t>(key_hash(key, n)) & ix.mask;
|
||||
const auto* const k_bytes = reinterpret_cast<const unsigned char*>(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
// (the loop for longer keys comes first, as the code is laid out; it is not the more common case)
|
||||
if (NLOHMANN_VIEW_UNLIKELY(n <= 16))
|
||||
{
|
||||
// (up to 16 bytes, the bytes are compared as two words, without a call)
|
||||
for (std::size_t distance = 0; distance <= index_max_displacement; ++distance)
|
||||
{
|
||||
const std::uint32_t s = table[slot];
|
||||
if (s == 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
const node* const k = obj + s;
|
||||
if (k->len == n && same_short_bytes(reinterpret_cast<const unsigned char*>(d.str(*k)), k_bytes, n)) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
{
|
||||
return k;
|
||||
}
|
||||
slot = (slot + 1) & ix.mask;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
for (std::size_t distance = 0; distance <= index_max_displacement; ++distance)
|
||||
{
|
||||
const std::uint32_t s = table[slot];
|
||||
|
||||
Reference in new issue
Block a user