mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 12062bc5bc | |||
| 6e0abb7f9c |
@@ -12,8 +12,6 @@
|
||||
#include <string_view>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
#if defined(__aarch64__) || defined(__ARM_NEON)
|
||||
#include <arm_neon.h>
|
||||
@@ -913,57 +911,6 @@ struct key_selector {
|
||||
return ki;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the JSON key at `p` (just past the opening quote, in a padded
|
||||
* buffer) against the compile-time key at selector index I. Because keys[I]
|
||||
* has a compile-time-constant length and bytes, the length check and memcmp
|
||||
* fully inline to a handful of fixed-width loads/compares -- the same cheap
|
||||
* comparison the ordered obj[key] path performs. The trailing-quote check
|
||||
* disambiguates keys that are prefixes of one another and of longer JSON keys.
|
||||
*/
|
||||
template <std::size_t I>
|
||||
static simdjson_really_inline bool matches_at(const char* p) noexcept {
|
||||
constexpr std::string_view k = keys[I];
|
||||
return p[k.size()] == '"' && std::memcmp(p, k.data(), k.size()) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Order-independent linear match: try each key in turn with a fully inlined,
|
||||
* compile-time-sized comparison, stopping at the first hit. For a small number
|
||||
* of keys this is cheaper than the perfect hash (no length scan, no table
|
||||
* loads), which matters because deserialization is dominated by many small
|
||||
* structs. Returns the selector index in [0, N) on match, or N on miss.
|
||||
*/
|
||||
static simdjson_really_inline std::size_t match_linear(raw_json_string rjs) noexcept {
|
||||
const char* p = rjs.raw();
|
||||
std::size_t idx = N;
|
||||
[&]<std::size_t... Is>(std::index_sequence<Is...>) {
|
||||
(void)((matches_at<Is>(p) ? (idx = Is, true) : false) || ...);
|
||||
}(std::make_index_sequence<N>{});
|
||||
return idx;
|
||||
}
|
||||
|
||||
// Selectors no larger than this use the unrolled linear matcher; larger ones
|
||||
// use the perfect hash. The crossover comes from a microbenchmark of both
|
||||
// matchers: for in-order hits (the deserialization case) linear wins up to
|
||||
// ~N=10, and for a pure miss the crossover is ~N=8, so 8 captures the small-
|
||||
// struct win without regressing larger or miss-heavy selectors. match_raw and
|
||||
// match_linear remain available if a caller wants to force one.
|
||||
static constexpr std::size_t linear_match_max = 8;
|
||||
|
||||
/**
|
||||
* Look up a JSON key, choosing the cheaper matcher for this selector's size:
|
||||
* the unrolled linear comparison for small selectors, the perfect hash for
|
||||
* large ones. Returns the selector index in [0, N) on match, or N on miss.
|
||||
*/
|
||||
static simdjson_really_inline std::size_t match(raw_json_string rjs) noexcept {
|
||||
if constexpr (N <= linear_match_max) {
|
||||
return match_linear(rjs);
|
||||
} else {
|
||||
return match_raw(rjs);
|
||||
}
|
||||
}
|
||||
|
||||
/** Return the key text at selector index i (i in [0, N)). */
|
||||
static constexpr std::string_view key_at(std::size_t i) noexcept {
|
||||
return keys[i];
|
||||
|
||||
@@ -66,34 +66,41 @@ simdjson_inline simdjson_result<value> object::find_field(const std::string_view
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
template <typename Selector, typename Func>
|
||||
simdjson_flatten simdjson_inline for_each_result object::for_each(Func&& on_match) noexcept {
|
||||
auto first = this->begin();
|
||||
if (first.error()) { return {first.error(), 0}; }
|
||||
object_iterator it = first.value_unsafe();
|
||||
object_iterator last{};
|
||||
// Single pass driven directly by the value_iterator, mirroring
|
||||
// find_field_unordered_raw + value(iter.child()). Compared to walking via
|
||||
// object_iterator/field, this avoids constructing a simdjson_result<field> and
|
||||
// a field (key + value) for every field -- and the development-check bookkeeping
|
||||
// in object_iterator -- building a value only for the fields that actually match.
|
||||
// We operate on a copy of the iterator, as object::begin() would.
|
||||
value_iterator it = iter;
|
||||
std::array<bool, Selector::size()> seen{};
|
||||
std::size_t matched = 0;
|
||||
while (it != last) {
|
||||
auto field_res = *it;
|
||||
if (field_res.error()) { return {field_res.error(), matched}; }
|
||||
field f = field_res.value_unsafe();
|
||||
std::size_t idx = Selector::match(f.key());
|
||||
while (it.is_open()) {
|
||||
raw_json_string key;
|
||||
error_code error = it.field_key().get(key);
|
||||
if (error) { it.abandon(); return {error, matched}; }
|
||||
// Advance past the ':' and descend onto the value.
|
||||
if ((error = it.field_value())) { it.abandon(); return {error, matched}; }
|
||||
std::size_t idx = Selector::match_raw(key);
|
||||
if (idx < Selector::size() && !seen[idx]) {
|
||||
seen[idx] = true;
|
||||
value matched_value = f.value();
|
||||
value matched_value(it.child());
|
||||
// The callback may return either void or an error_code. When it returns an
|
||||
// error_code, we stop at the first non-SUCCESS result and propagate it so
|
||||
// the caller can surface value-parse errors (for example, a type mismatch
|
||||
// on a matched field). A void-returning callback is responsible for
|
||||
// handling its own errors.
|
||||
if constexpr (std::is_same_v<decltype(on_match(idx, matched_value)), error_code>) {
|
||||
error_code e = on_match(idx, matched_value);
|
||||
if (e) { return {e, matched}; }
|
||||
if ((error = on_match(idx, matched_value))) { return {error, matched}; }
|
||||
} else {
|
||||
on_match(idx, matched_value);
|
||||
}
|
||||
if (++matched >= Selector::size()) { break; }
|
||||
}
|
||||
++it;
|
||||
// Skip the value (a no-op if the callback consumed it) and step to the next
|
||||
// field; has_next_field() ends the container on '}', which closes the loop.
|
||||
if ((error = it.skip_child())) { it.abandon(); return {error, matched}; }
|
||||
if ((error = it.has_next_field().error())) { return {error, matched}; }
|
||||
}
|
||||
return {SUCCESS, matched};
|
||||
}
|
||||
|
||||
@@ -345,42 +345,41 @@ namespace object_tests {
|
||||
#endif
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
// The key_selector exposes two matchers -- the perfect hash (match_raw) and the
|
||||
// unrolled linear scan (match_linear) -- and match() dispatches between them by
|
||||
// size. They must return identical selector indices for every key, including
|
||||
// tricky cases: keys that are prefixes of one another, keys that extend a real
|
||||
// key, a long key, and misses. This guards the linear matcher's prefix/length
|
||||
// disambiguation (the trailing-quote check) against the hash.
|
||||
// The key_selector's matcher (match_raw, the perfect hash) must return the
|
||||
// right selector index for every key, including tricky cases: keys that are
|
||||
// prefixes of one another, keys that extend a real key, a long key, and misses.
|
||||
// This guards the prefix/length disambiguation against the hash.
|
||||
bool key_selector_matchers_agree() {
|
||||
TEST_START();
|
||||
// Probe builds "<key>\"" in a padded buffer and checks the three matchers agree.
|
||||
auto probe = [](auto sel_tag, std::string_view key) -> bool {
|
||||
// Probe builds "<key>\"" in a padded buffer and checks match_raw returns the
|
||||
// expected selector index (or N for a miss).
|
||||
auto probe = [](auto sel_tag, std::string_view key, std::size_t expected) -> bool {
|
||||
using sel = decltype(sel_tag);
|
||||
char buf[64] = {};
|
||||
for (size_t i = 0; i < key.size(); ++i) { buf[i] = key[i]; }
|
||||
buf[key.size()] = '"';
|
||||
ondemand::raw_json_string r(reinterpret_cast<const uint8_t*>(buf));
|
||||
std::size_t raw = sel::match_raw(r);
|
||||
ASSERT_EQUAL(sel::match_linear(r), raw);
|
||||
ASSERT_EQUAL(sel::match(r), raw);
|
||||
ASSERT_EQUAL(sel::match_raw(r), expected);
|
||||
return true;
|
||||
};
|
||||
// Small selector (<= linear_match_max, so match() uses the linear scan),
|
||||
// with prefix keys and a 30-character key.
|
||||
// Selector with prefix keys and a 30-character key.
|
||||
using small_sel = ondemand::key_selector<"a", "ab", "abc", "id", "name",
|
||||
"abcdefghijklmnopqrstuvwxyz1234">;
|
||||
std::size_t i = 0;
|
||||
for (auto k : {"a", "ab", "abc", "id", "name", "abcdefghijklmnopqrstuvwxyz1234"}) {
|
||||
if (!probe(small_sel{}, k)) { return false; }
|
||||
if (!probe(small_sel{}, k, i++)) { return false; }
|
||||
}
|
||||
for (auto k : {"x", "abcd", "nam", "names", "i", "ids", "zzzzz", ""}) {
|
||||
if (!probe(small_sel{}, k)) { return false; }
|
||||
if (!probe(small_sel{}, k, small_sel::size())) { return false; }
|
||||
}
|
||||
// Large selector (> linear_match_max, so match() uses the perfect hash);
|
||||
// match_linear must still agree with the hash.
|
||||
// Larger selector exercising the same matcher.
|
||||
using big_sel = ondemand::key_selector<"k00","k01","k02","k03","k04","k05",
|
||||
"k06","k07","k08","k09","k10","k11">;
|
||||
for (auto k : {"k00","k05","k11","k12","nope",""}) {
|
||||
if (!probe(big_sel{}, k)) { return false; }
|
||||
if (!probe(big_sel{}, "k00", 0)) { return false; }
|
||||
if (!probe(big_sel{}, "k05", 5)) { return false; }
|
||||
if (!probe(big_sel{}, "k11", 11)) { return false; }
|
||||
for (auto k : {"k12","nope",""}) {
|
||||
if (!probe(big_sel{}, k, big_sel::size())) { return false; }
|
||||
}
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user