Skip to content

Commit

Permalink
Merge pull request #16 from codetiger/main
Browse files Browse the repository at this point in the history
Performance improvements
  • Loading branch information
codetiger authored Dec 19, 2024
2 parents c1ef9b2 + 8a9cc4d commit 0bff125
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 110 deletions.
5 changes: 5 additions & 0 deletions src/rule/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ pub enum Rule {
}

impl Rule {
#[inline(always)]
fn is_static(&self) -> bool {
match self {
Rule::Value(_) => true,
Expand Down Expand Up @@ -158,13 +159,15 @@ impl Rule {
}
}

#[inline]
fn optimize_args(args: &[Rule]) -> Result<Vec<Rule>, Error> {
args.iter()
.cloned()
.map(Self::optimize_rule)
.collect()
}

#[inline]
fn rebuild_with_args(rule: Rule, optimized: Vec<Rule>) -> Rule {
match rule {
Rule::Map(_) => Rule::Map(optimized),
Expand Down Expand Up @@ -206,6 +209,7 @@ impl Rule {
}
}

#[inline]
fn optimize_rule(rule: Rule) -> Result<Rule, Error> {
match rule {
// Never optimize these
Expand Down Expand Up @@ -348,6 +352,7 @@ impl Rule {
}
}

#[inline(always)]
pub fn apply(&self, data: &Value) -> Result<Value, Error> {
match self {
Rule::Value(value) => {
Expand Down
59 changes: 23 additions & 36 deletions src/rule/operators/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,49 +54,36 @@ impl Operator for FilterOperator {

impl Operator for ReduceOperator {
fn apply(&self, args: &[Rule], data: &Value) -> Result<Value, Error> {
// Static keys to avoid repeated allocations
static CURRENT: &str = "current";
static ACCUMULATOR: &str = "accumulator";

match args {
[array_rule, reducer_rule, initial_rule] => {
match array_rule.apply(data)? {
// Fast path: empty array
Value::Array(arr) if arr.is_empty() => initial_rule.apply(data),

// Fast path: single element
Value::Array(arr) if arr.len() == 1 => {
let mut item_data = Value::Object(serde_json::Map::with_capacity(2));
let accumulator = initial_rule.apply(data)?;
item_data["current"] = arr[0].clone();
item_data["accumulator"] = accumulator;
reducer_rule.apply(&item_data)
},

// Fast path: small arrays (2-4 elements)
Value::Array(arr) if arr.len() <= 4 => {
let mut item_data = Value::Object(serde_json::Map::with_capacity(2));
let mut accumulator = initial_rule.apply(data)?;

// Unrolled loop for small arrays
for item in arr {
item_data["current"] = item;
item_data["accumulator"] = accumulator;
accumulator = reducer_rule.apply(&item_data)?;
}
Ok(accumulator)
},

// Regular path: larger arrays
Value::Array(arr) => {
let mut item_data = Value::Object(serde_json::Map::with_capacity(2));
let mut accumulator = initial_rule.apply(data)?;

// Process in chunks of 4 for better cache utilization
for chunk in arr.chunks(4) {
for item in chunk {
item_data["current"] = item.clone();
item_data["accumulator"] = accumulator;
accumulator = reducer_rule.apply(&item_data)?;
let mut map = serde_json::Map::with_capacity(2);
map.insert(CURRENT.to_string(), Value::Null);
map.insert(ACCUMULATOR.to_string(), initial_rule.apply(data)?);
let mut item_data = Value::Object(map);

for item in arr {
if let Value::Object(ref mut map) = item_data {
map.insert(CURRENT.to_string(), item);
}

let result = reducer_rule.apply(&item_data)?;

if let Value::Object(ref mut map) = item_data {
map.insert(ACCUMULATOR.to_string(), result);
}
}

match item_data {
Value::Object(map) => Ok(map.get(ACCUMULATOR).cloned().unwrap_or(Value::Null)),
_ => Ok(Value::Null)
}
Ok(accumulator)
},
_ => initial_rule.apply(data),
}
Expand Down
228 changes: 164 additions & 64 deletions src/rule/operators/comparison.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,116 +14,216 @@ pub struct LessThanEqualOperator;

impl Operator for EqualsOperator {
fn apply(&self, args: &[Rule], data: &Value) -> Result<Value, Error> {
if args.len() != 2 {
return Err(Error::InvalidArguments("== requires 2 arguments".to_string()));
match args {
[a, b] => {
let left = a.apply(data)?;
let right = b.apply(data)?;

Ok(Value::Bool(match (&left, &right) {
(Value::Number(n1), Value::Number(n2)) => n1 == n2,
(Value::String(s1), Value::String(s2)) => s1 == s2,
(Value::Bool(b1), Value::Bool(b2)) => b1 == b2,
_ => left.coerce_to_number() == right.coerce_to_number()
}))
},
args if args.len() > 2 => {
let mut prev = args[0].apply(data)?;
for arg in args.iter().skip(1) {
let curr = arg.apply(data)?;
if std::mem::discriminant(&prev) == std::mem::discriminant(&curr) || prev == curr {
return Ok(Value::Bool(false));
}
prev = curr;
}
Ok(Value::Bool(true))
}
_ => Err(Error::InvalidArguments("==".into()))
}
let left = args[0].apply(data)?;
let right = args[1].apply(data)?;

Ok(Value::Bool(left.coerce_to_number() == right.coerce_to_number()))
}
}

impl Operator for StrictEqualsOperator {
fn apply(&self, args: &[Rule], data: &Value) -> Result<Value, Error> {
if args.len() != 2 {
return Err(Error::InvalidArguments("=== requires 2 arguments".to_string()));
match args {
[a, b] => {
let left = a.apply(data)?;
let right = b.apply(data)?;

Ok(Value::Bool(std::mem::discriminant(&left) == std::mem::discriminant(&right) && left == right))
}
args if args.len() > 2 => {
let mut prev = args[0].apply(data)?;
for arg in args.iter().skip(1) {
let curr = arg.apply(data)?;
if std::mem::discriminant(&prev) != std::mem::discriminant(&curr) || prev != curr {
return Ok(Value::Bool(false));
}
prev = curr;
}
Ok(Value::Bool(true))
}
_ => Err(Error::InvalidArguments("===".into()))
}
let left = args[0].apply(data)?;
let right = args[1].apply(data)?;
Ok(Value::Bool(std::mem::discriminant(&left) == std::mem::discriminant(&right) && left == right))
}
}

impl Operator for NotEqualsOperator {
fn apply(&self, args: &[Rule], data: &Value) -> Result<Value, Error> {
if args.len() < 2 {
return Err(Error::InvalidArguments("!= requires at least 2 arguments".to_string()));
}
let left = args[0].apply(data)?;
let right = args[1].apply(data)?;

match (&left, &right) {
(Value::String(s), Value::Number(_)) | (Value::Number(_), Value::String(s)) => {
if s.parse::<f64>().is_ok() {
return Ok(Value::Bool(left.coerce_to_number() != right.coerce_to_number()));
match args {
[a, b] => {
let left = a.apply(data)?;
let right = b.apply(data)?;

Ok(Value::Bool(match (&left, &right) {
(Value::Number(n1), Value::Number(n2)) => n1 != n2,
(Value::String(s1), Value::String(s2)) => s1 != s2,
(Value::Bool(b1), Value::Bool(b2)) => b1 != b2,
_ => left.coerce_to_number() != right.coerce_to_number()
}))
}
args if args.len() > 2 => {
let mut prev = args[0].apply(data)?;
for arg in args.iter().skip(1) {
let curr = arg.apply(data)?;
let not_equal = match (&prev, &curr) {
(Value::Number(n1), Value::Number(n2)) => n1 != n2,
(Value::String(s1), Value::String(s2)) => s1 != s2,
(Value::Bool(b1), Value::Bool(b2)) => b1 != b2,
_ => prev.coerce_to_number() != curr.coerce_to_number()
};
if not_equal {
return Ok(Value::Bool(true));
}
prev = curr;
}
},
_ => {}
Ok(Value::Bool(false))
}
_ => Err(Error::InvalidArguments("!=".into()))
}
Ok(Value::Bool(left != right))
}
}

impl Operator for StrictNotEqualsOperator {
fn apply(&self, args: &[Rule], data: &Value) -> Result<Value, Error> {
if args.len() < 2 {
return Err(Error::InvalidArguments("!== requires at least 2 arguments".to_string()));
match args {
[a, b] => {
let left = a.apply(data)?;
let right = b.apply(data)?;

Ok(Value::Bool(std::mem::discriminant(&left) != std::mem::discriminant(&right) || left != right))
}
args if args.len() > 2 => {
let mut prev = args[0].apply(data)?;
for arg in args.iter().skip(1) {
let curr = arg.apply(data)?;
if std::mem::discriminant(&prev) != std::mem::discriminant(&curr) || prev != curr {
return Ok(Value::Bool(true));
}
prev = curr;
}
Ok(Value::Bool(false))
}
_ => Err(Error::InvalidArguments("!==".into()))
}
let left = args[0].apply(data)?;
let right = args[1].apply(data)?;
Ok(Value::Bool(std::mem::discriminant(&left) != std::mem::discriminant(&right) || left != right))
}
}

impl Operator for GreaterThanOperator {
fn apply(&self, args: &[Rule], data: &Value) -> Result<Value, Error> {
if args.len() < 2 {
return Err(Error::InvalidArguments("> requires at least 2 arguments".to_string()));
match args {
[a, b] => {
let left = a.apply(data)?;
let right = b.apply(data)?;

Ok(Value::Bool(left.coerce_to_number() > right.coerce_to_number()))
},
args if args.len() > 2 => {
let mut prev = args[0].apply(data)?;
for arg in args.iter().skip(1) {
let curr = arg.apply(data)?;
if prev.coerce_to_number() <= curr.coerce_to_number() {
return Ok(Value::Bool(false));
}
prev = curr;
}
Ok(Value::Bool(true))
}
_ => Err(Error::InvalidArguments(">".into()))
}
let left = args[0].apply(data)?;
let right = args[1].apply(data)?;
Ok(Value::Bool(left.coerce_to_number() > right.coerce_to_number()))
}
}

impl Operator for LessThanOperator {
fn apply(&self, args: &[Rule], data: &Value) -> Result<Value, Error> {
if args.len() < 2 {
return Err(Error::InvalidArguments("< requires at least 2 arguments".to_string()));
}
let mut current = args[0].apply(data)?.coerce_to_number();
for arg in &args[1..] {
let next = arg.apply(data)?.coerce_to_number();
if current >= next {
return Ok(Value::Bool(false));
match args {
[a, b] => {
let left = a.apply(data)?;
let right = b.apply(data)?;

Ok(Value::Bool(left.coerce_to_number() < right.coerce_to_number()))
}
args if args.len() > 2 => {
let mut prev = args[0].apply(data)?;
for arg in args.iter().skip(1) {
let curr = arg.apply(data)?;
if prev.coerce_to_number() >= curr.coerce_to_number() {
return Ok(Value::Bool(false));
}
prev = curr;
}
Ok(Value::Bool(true))
}
current = next;
_ => Err(Error::InvalidArguments("<".into()))
}
Ok(Value::Bool(true))
}
}

impl Operator for LessThanEqualOperator {
fn apply(&self, args: &[Rule], data: &Value) -> Result<Value, Error> {
if args.len() < 2 {
return Err(Error::InvalidArguments("<= requires at least 2 arguments".to_string()));
}
let mut current = args[0].apply(data)?.coerce_to_number();
for arg in &args[1..] {
let next = arg.apply(data)?.coerce_to_number();
if current > next {
return Ok(Value::Bool(false));
match args {
[a, b] => {
let left = a.apply(data)?;
let right = b.apply(data)?;

Ok(Value::Bool(left.coerce_to_number() <= right.coerce_to_number()))
}
args if args.len() > 2 => {
let mut prev = args[0].apply(data)?;
for arg in args.iter().skip(1) {
let curr = arg.apply(data)?;
if prev.coerce_to_number() > curr.coerce_to_number() {
return Ok(Value::Bool(false));
}
prev = curr;
}
Ok(Value::Bool(true))
}
current = next;
_ => Err(Error::InvalidArguments("<=".into()))
}
Ok(Value::Bool(true))
}
}

impl Operator for GreaterThanEqualOperator {
fn apply(&self, args: &[Rule], data: &Value) -> Result<Value, Error> {
if args.len() < 2 {
return Err(Error::InvalidArguments(">= requires at least 2 arguments".to_string()));
}
let mut current = args[0].apply(data)?.coerce_to_number();
for arg in &args[1..] {
let next = arg.apply(data)?.coerce_to_number();
if current < next {
return Ok(Value::Bool(false));
match args {
[a, b] => {
let left = a.apply(data)?;
let right = b.apply(data)?;

Ok(Value::Bool(left.coerce_to_number() >= right.coerce_to_number()))
}
args if args.len() > 2 => {
let mut prev = args[0].apply(data)?;
for arg in args.iter().skip(1) {
let curr = arg.apply(data)?;
if prev.coerce_to_number() < curr.coerce_to_number() {
return Ok(Value::Bool(false));
}
prev = curr;
}
Ok(Value::Bool(true))
}
current = next;
_ => Err(Error::InvalidArguments(">=".into()))
}
Ok(Value::Bool(true))
}
}
Loading

0 comments on commit 0bff125

Please sign in to comment.