Skip to content

Commit 8910617

Browse files
authored
perf: improve performance of the index selector
added more structure and metadata to the automaton, improving perf of all queries in general (~6% thpt) and array-index queries in particular (~12% thpt) ref: #138
1 parent 09b26aa commit 8910617

File tree

8 files changed

+415
-338
lines changed

8 files changed

+415
-338
lines changed

.gitattributes

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
* eol=lf
2-
/pdf/* -text
2+
/pdf/* -text
3+
/crates/rsonpath-syntax/tests/error_snapshots.rs diff

crates/rsonpath-lib/src/automaton.rs

+46-115
Original file line numberDiff line numberDiff line change
@@ -19,79 +19,10 @@ pub struct Automaton<'q> {
1919
states: Vec<StateTable<'q>>,
2020
}
2121

22-
/// Represent the distinct methods of moving on a match between states.
23-
#[derive(Debug, Copy, PartialEq, Clone, Eq)]
24-
pub enum TransitionLabel<'q> {
25-
/// Transition when a JSON member name matches a [`JsonString`]i.
26-
ObjectMember(&'q JsonString),
27-
/// Transition on the n-th element of an array, with n specified by a [`JsonUInt`].
28-
ArrayIndex(JsonUInt),
29-
}
30-
31-
impl<'q> TransitionLabel<'q> {
32-
///Return the textual [`JsonString`] being wrapped if so. Returns [`None`] otherwise.
33-
#[must_use]
34-
#[inline(always)]
35-
pub fn get_member_name(&self) -> Option<&'q JsonString> {
36-
match self {
37-
TransitionLabel::ObjectMember(name) => Some(name),
38-
TransitionLabel::ArrayIndex(_) => None,
39-
}
40-
}
41-
42-
///Return the [`JsonUInt`] being wrapped if so. Returns [`None`] otherwise.
43-
#[must_use]
44-
#[inline(always)]
45-
pub fn get_array_index(&'q self) -> Option<&'q JsonUInt> {
46-
match self {
47-
TransitionLabel::ArrayIndex(name) => Some(name),
48-
TransitionLabel::ObjectMember(_) => None,
49-
}
50-
}
51-
52-
/// Wraps a [`JsonString`] in a [`TransitionLabel`].
53-
#[must_use]
54-
#[inline(always)]
55-
pub fn new_object_member(member_name: &'q JsonString) -> Self {
56-
TransitionLabel::ObjectMember(member_name)
57-
}
58-
59-
/// Wraps a [`JsonUInt`] in a [`TransitionLabel`].
60-
#[must_use]
61-
#[inline(always)]
62-
pub fn new_array_index(index: JsonUInt) -> Self {
63-
TransitionLabel::ArrayIndex(index)
64-
}
65-
}
66-
67-
impl<'q> From<&'q JsonString> for TransitionLabel<'q> {
68-
#[must_use]
69-
#[inline(always)]
70-
fn from(member_name: &'q JsonString) -> Self {
71-
TransitionLabel::new_object_member(member_name)
72-
}
73-
}
74-
75-
impl Display for TransitionLabel<'_> {
76-
#[inline(always)]
77-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78-
match self {
79-
TransitionLabel::ObjectMember(name) => write!(f, "{}", name.unquoted()),
80-
TransitionLabel::ArrayIndex(index) => write!(f, "{}", index.as_u64()),
81-
}
82-
}
83-
}
84-
85-
impl From<JsonUInt> for TransitionLabel<'_> {
86-
#[must_use]
87-
#[inline(always)]
88-
fn from(index: JsonUInt) -> Self {
89-
TransitionLabel::new_array_index(index)
90-
}
91-
}
92-
93-
/// A single transition of an [`Automaton`].
94-
type Transition<'q> = (TransitionLabel<'q>, State);
22+
/// Transition when a JSON member name matches a [`JsonString`]i.
23+
pub type MemberTransition<'q> = (&'q JsonString, State);
24+
/// Transition on the n-th element of an array, with n specified by a [`JsonUInt`].
25+
pub type ArrayTransition<'q> = (JsonUInt, State);
9526

9627
/// A transition table of a single [`State`] of an [`Automaton`].
9728
///
@@ -100,7 +31,8 @@ type Transition<'q> = (TransitionLabel<'q>, State);
10031
#[derive(Debug)]
10132
pub struct StateTable<'q> {
10233
attributes: StateAttributes,
103-
transitions: SmallVec<[Transition<'q>; 2]>,
34+
member_transitions: SmallVec<[MemberTransition<'q>; 2]>,
35+
array_transitions: SmallVec<[ArrayTransition<'q>; 2]>,
10436
fallback_state: State,
10537
}
10638

@@ -109,7 +41,8 @@ impl<'q> Default for StateTable<'q> {
10941
fn default() -> Self {
11042
Self {
11143
attributes: StateAttributes::default(),
112-
transitions: Default::default(),
44+
member_transitions: SmallVec::default(),
45+
array_transitions: SmallVec::default(),
11346
fallback_state: State(0),
11447
}
11548
}
@@ -118,11 +51,17 @@ impl<'q> Default for StateTable<'q> {
11851
impl<'q> PartialEq for StateTable<'q> {
11952
#[inline]
12053
fn eq(&self, other: &Self) -> bool {
121-
self.fallback_state == other.fallback_state
122-
&& self.transitions.len() == other.transitions.len()
123-
&& self.transitions.iter().all(|x| other.transitions.contains(x))
124-
&& other.transitions.iter().all(|x| self.transitions.contains(x))
125-
&& self.attributes == other.attributes
54+
return self.fallback_state == other.fallback_state
55+
&& set_eq(&self.array_transitions, &other.array_transitions)
56+
&& set_eq(&self.member_transitions, &other.member_transitions)
57+
&& self.attributes == other.attributes;
58+
59+
#[inline(always)]
60+
fn set_eq<T: Eq, A: smallvec::Array<Item = T>>(left: &SmallVec<A>, right: &SmallVec<A>) -> bool {
61+
left.len() == right.len()
62+
&& left.iter().all(|x| right.contains(x))
63+
&& right.iter().all(|x| left.contains(x))
64+
}
12665
}
12766
}
12867

@@ -209,7 +148,7 @@ impl<'q> Automaton<'q> {
209148
/// # use rsonpath::automaton::*;
210149
/// let query = rsonpath_syntax::parse("$.a").unwrap();
211150
/// let automaton = Automaton::new(&query).unwrap();
212-
/// let state_2 = automaton[automaton.initial_state()].transitions()[0].1;
151+
/// let state_2 = automaton[automaton.initial_state()].member_transitions()[0].1;
213152
///
214153
/// assert!(automaton.is_accepting(state_2));
215154
/// ```
@@ -233,30 +172,7 @@ impl<'q> Automaton<'q> {
233172
#[must_use]
234173
#[inline(always)]
235174
pub fn has_any_array_item_transition(&self, state: State) -> bool {
236-
self[state]
237-
.transitions()
238-
.iter()
239-
.any(|t| matches!(t, (TransitionLabel::ArrayIndex(_), _)))
240-
}
241-
242-
/// Returns whether the given state is accepting an item in a list.
243-
///
244-
/// # Example
245-
/// ```rust
246-
/// # use rsonpath::automaton::*;
247-
/// let query = rsonpath_syntax::parse("$[2]").unwrap();
248-
/// let automaton = Automaton::new(&query).unwrap();
249-
/// let state = automaton.initial_state();
250-
///
251-
/// assert!(automaton.has_any_array_item_transition_to_accepting(state));
252-
/// ```
253-
#[must_use]
254-
#[inline(always)]
255-
pub fn has_any_array_item_transition_to_accepting(&self, state: State) -> bool {
256-
self[state].transitions().iter().any(|t| match t {
257-
(TransitionLabel::ArrayIndex(_), s) => self.is_accepting(*s),
258-
_ => false,
259-
})
175+
self[state].attributes.has_array_index_transition()
260176
}
261177

262178
/// Returns whether the given state is accepting the first item in a list.
@@ -302,10 +218,12 @@ impl<'q> Automaton<'q> {
302218
#[must_use]
303219
#[inline(always)]
304220
pub fn has_array_index_transition_to_accepting(&self, state: State, match_index: &JsonUInt) -> bool {
305-
self[state].transitions().iter().any(|t| match t {
306-
(TransitionLabel::ArrayIndex(i), s) => i.eq(match_index) && self.is_accepting(*s),
307-
_ => false,
308-
})
221+
let state = &self[state];
222+
state.attributes.has_array_index_transition_to_accepting()
223+
&& state
224+
.array_transitions()
225+
.iter()
226+
.any(|(i, s)| i.eq(match_index) && self.is_accepting(*s))
309227
}
310228

311229
/// Returns whether the given state has any transitions
@@ -379,14 +297,24 @@ impl<'q> StateTable<'q> {
379297
self.fallback_state
380298
}
381299

382-
/// Returns the collection of labelled transitions from this state.
300+
/// Returns the collection of labelled array transitions from this state.
383301
///
384-
/// A transition is triggered if the [`TransitionLabel`] is matched and leads
302+
/// A transition is triggered if the [`ArrayTransition`] is matched and leads
385303
/// to the contained [`State`].
386304
#[must_use]
387305
#[inline(always)]
388-
pub fn transitions(&self) -> &[Transition<'q>] {
389-
&self.transitions
306+
pub fn array_transitions(&self) -> &[ArrayTransition<'q>] {
307+
&self.array_transitions
308+
}
309+
310+
/// Returns the collection of labelled member transitions from this state.
311+
///
312+
/// A transition is triggered if the [`MemberTransition`] is matched and leads
313+
/// to the contained [`State`].
314+
#[must_use]
315+
#[inline(always)]
316+
pub fn member_transitions(&self) -> &[MemberTransition<'q>] {
317+
&self.member_transitions
390318
}
391319
}
392320

@@ -420,8 +348,11 @@ impl<'q> Display for Automaton<'q> {
420348
}
421349

422350
for (i, transitions) in self.states.iter().enumerate() {
423-
for (label, state) in &transitions.transitions {
424-
writeln!(f, " {i} -> {} [label=\"{}\"]", state.0, label,)?
351+
for (label, state) in &transitions.array_transitions {
352+
writeln!(f, " {i} -> {} [label=\"[{}]\"]", state.0, label.as_u64())?
353+
}
354+
for (label, state) in &transitions.member_transitions {
355+
writeln!(f, " {i} -> {} [label=\"{}\"]", state.0, label.unquoted())?
425356
}
426357
writeln!(f, " {i} -> {} [label=\"*\"]", transitions.fallback_state.0)?;
427358
}

0 commit comments

Comments
 (0)