From 953b39d43389320765a0f831c75cb04b7b8e8f13 Mon Sep 17 00:00:00 2001 From: Duhan BALCI Date: Mon, 6 Apr 2026 02:51:42 +0300 Subject: [PATCH] perf improvements --- benches/my_benchmark.rs | 7 ++++--- src/ast/value.rs | 27 ++++++++++++++------------- src/language_info.rs | 3 ++- src/vm/methods.rs | 23 ++++++++++++----------- src/vm/vm.rs | 3 ++- tests/data_driven_tests.rs | 3 ++- tests/integration_tests.rs | 21 +++++++++++---------- 7 files changed, 47 insertions(+), 40 deletions(-) diff --git a/benches/my_benchmark.rs b/benches/my_benchmark.rs index a404b3e..eb2ccca 100644 --- a/benches/my_benchmark.rs +++ b/benches/my_benchmark.rs @@ -4,6 +4,7 @@ use indexmap::IndexMap; use rust_decimal::Decimal; use rust_decimal_macros::dec; use smol_str::SmolStr; +use std::rc::Rc; /// Helper: compile source to bytecode fn compile(source: &str) -> Vec { @@ -18,7 +19,7 @@ fn sample_object(n: usize) -> Value { for i in 0..n { map.insert(SmolStr::new(format!("field{}", i)), Value::Number(Decimal::from(i))); } - Value::Object(Box::new(map)) + Value::Object(Rc::new(map)) } pub fn criterion_benchmark(c: &mut Criterion) { @@ -110,7 +111,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { // StringList method — clone entire Vec per call c.bench_function("vm_strlist_method_length", |b| { let bytecode = compile("items.length()"); - let items = Value::StringList(Box::new((0..50).map(|i| SmolStr::new(format!("item{}", i))).collect())); + let items = Value::StringList(Rc::new((0..50).map(|i| SmolStr::new(format!("item{}", i))).collect())); b.iter(|| { let mut vm = VM::new(&bytecode); vm.set_global("items", items.clone()); @@ -120,7 +121,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { c.bench_function("vm_numlist_method_sum", |b| { let bytecode = compile("nums.sum()"); - let nums = Value::NumberList(Box::new((0..100).map(Decimal::from).collect())); + let nums = Value::NumberList(Rc::new((0..100).map(Decimal::from).collect())); b.iter(|| { let mut vm = VM::new(&bytecode); vm.set_global("nums", nums.clone()); diff --git a/src/ast/value.rs b/src/ast/value.rs index 31ae253..16e789e 100644 --- a/src/ast/value.rs +++ b/src/ast/value.rs @@ -2,6 +2,7 @@ use indexmap::IndexMap; use rust_decimal::Decimal; use smol_str::SmolStr; use std::fmt; +use std::rc::Rc; /// Value type for the dExpr language #[derive(Debug, Clone, PartialEq, Default)] @@ -11,9 +12,9 @@ pub enum Value { Number(Decimal), String(SmolStr), Boolean(bool), - NumberList(Box>), - StringList(Box>), - Object(Box>), + NumberList(Rc>), + StringList(Rc>), + Object(Rc>), } /// Type tag constants for serialization @@ -209,19 +210,19 @@ impl From for Value { impl From> for Value { fn from(v: Vec) -> Self { - Value::NumberList(Box::new(v)) + Value::NumberList(Rc::new(v)) } } impl From> for Value { fn from(v: Vec) -> Self { - Value::StringList(Box::new(v)) + Value::StringList(Rc::new(v)) } } impl From> for Value { fn from(m: IndexMap) -> Self { - Value::Object(Box::new(m)) + Value::Object(Rc::new(m)) } } @@ -292,7 +293,7 @@ impl Value { list.push(Decimal::deserialize(decimal_bytes)); } - Ok((Value::NumberList(Box::new(list)), pos)) + Ok((Value::NumberList(Rc::new(list)), pos)) } TYPE_STRING_LIST => { if bytes.len() < pos + 2 { @@ -321,7 +322,7 @@ impl Value { list.push(s.into()); } - Ok((Value::StringList(Box::new(list)), pos)) + Ok((Value::StringList(Rc::new(list)), pos)) } TYPE_OBJECT => { if bytes.len() < pos + 2 { @@ -354,7 +355,7 @@ impl Value { map.insert(key.into(), val); } - Ok((Value::Object(Box::new(map)), pos)) + Ok((Value::Object(Rc::new(map)), pos)) } _ => Err(format!("Unknown type tag: {}", type_tag)), } @@ -407,7 +408,7 @@ impl Value { serde_json::Value::String(s) => Ok(Value::String(SmolStr::new(s))), serde_json::Value::Array(arr) => { if arr.is_empty() { - return Ok(Value::StringList(Box::new(Vec::new()))); + return Ok(Value::StringList(Rc::new(Vec::new()))); } // Check if all elements are the same type let first = &arr[0]; @@ -418,12 +419,12 @@ impl Value { nums.push(n); } } - Ok(Value::NumberList(Box::new(nums))) + Ok(Value::NumberList(Rc::new(nums))) } else if first.is_string() && arr.iter().all(|v| v.is_string()) { let strings: Vec = arr.iter() .filter_map(|v| v.as_str().map(SmolStr::new)) .collect(); - Ok(Value::StringList(Box::new(strings))) + Ok(Value::StringList(Rc::new(strings))) } else { Err("Arrays must contain all numbers or all strings".to_string()) } @@ -433,7 +434,7 @@ impl Value { for (k, v) in obj { map.insert(SmolStr::new(k), Self::from_json_value(v)?); } - Ok(Value::Object(Box::new(map))) + Ok(Value::Object(Rc::new(map))) } } } diff --git a/src/language_info.rs b/src/language_info.rs index 142fc13..234e131 100644 --- a/src/language_info.rs +++ b/src/language_info.rs @@ -11,6 +11,7 @@ //! use indexmap::IndexMap; //! use smol_str::SmolStr; //! use rust_decimal_macros::dec; +//! use std::rc::Rc; //! //! let mut info = LanguageInfo::builtin(); //! @@ -24,7 +25,7 @@ //! let mut customer = IndexMap::new(); //! customer.insert(SmolStr::new("name"), Value::String("Alice".into())); //! customer.insert(SmolStr::new("age"), Value::Number(dec!(30))); -//! info.add_value("customer", &Value::Object(Box::new(customer)), None); +//! info.add_value("customer", &Value::Object(Rc::new(customer)), None); //! info.add_value("price", &Value::Number(dec!(100)), None); //! //! let json = info.to_json(); diff --git a/src/vm/methods.rs b/src/vm/methods.rs index 8dbabd6..397a801 100644 --- a/src/vm/methods.rs +++ b/src/vm/methods.rs @@ -1,6 +1,7 @@ use crate::ast::value::Value; use rust_decimal::{prelude::ToPrimitive, Decimal}; use smol_str::{SmolStr, StrExt}; +use std::rc::Rc; use super::error::VMError; use super::vm::VM; @@ -72,7 +73,7 @@ impl<'a> VM<'a> { match &args[0] { Value::String(delim) => { let parts: Vec = s.split(delim.as_str()).map(SmolStr::new).collect(); - Ok(Value::StringList(Box::new(parts))) + Ok(Value::StringList(Rc::new(parts))) } _ => Err(VMError::RuntimeError( "split() requires a string delimiter".to_string(), @@ -263,7 +264,7 @@ impl<'a> VM<'a> { } else { list.len() }; - Ok(Value::StringList(Box::new(list[start..end].to_vec()))) + Ok(Value::StringList(Rc::new(list[start..end].to_vec()))) } _ => Err(VMError::RuntimeError("slice() requires a number index".to_string())), } @@ -271,12 +272,12 @@ impl<'a> VM<'a> { "reverse" => { let mut reversed = list.to_vec(); reversed.reverse(); - Ok(Value::StringList(Box::new(reversed))) + Ok(Value::StringList(Rc::new(reversed))) } "sort" => { let mut sorted = list.to_vec(); sorted.sort(); - Ok(Value::StringList(Box::new(sorted))) + Ok(Value::StringList(Rc::new(sorted))) } "join" => { let delim = if args.is_empty() { @@ -368,7 +369,7 @@ impl<'a> VM<'a> { } else { list.len() }; - Ok(Value::NumberList(Box::new(list[start..end].to_vec()))) + Ok(Value::NumberList(Rc::new(list[start..end].to_vec()))) } _ => Err(VMError::RuntimeError("slice() requires a number index".to_string())), } @@ -376,12 +377,12 @@ impl<'a> VM<'a> { "reverse" => { let mut reversed = list.to_vec(); reversed.reverse(); - Ok(Value::NumberList(Box::new(reversed))) + Ok(Value::NumberList(Rc::new(reversed))) } "sort" => { let mut sorted = list.to_vec(); sorted.sort(); - Ok(Value::NumberList(Box::new(sorted))) + Ok(Value::NumberList(Rc::new(sorted))) } "sum" => { let sum: Decimal = list.iter().sum(); @@ -426,12 +427,12 @@ impl<'a> VM<'a> { match method { "keys" => { let keys: Vec = map.keys().cloned().collect(); - Ok(Value::StringList(Box::new(keys))) + Ok(Value::StringList(Rc::new(keys))) } "values" => { let vals: Vec = map.values().cloned().collect(); if vals.is_empty() { - Ok(Value::StringList(Box::new(Vec::new()))) + Ok(Value::StringList(Rc::new(Vec::new()))) } else if vals.iter().all(|v| matches!(v, Value::String(_))) { let strings: Vec = vals .into_iter() @@ -440,7 +441,7 @@ impl<'a> VM<'a> { _ => unreachable!(), }) .collect(); - Ok(Value::StringList(Box::new(strings))) + Ok(Value::StringList(Rc::new(strings))) } else if vals.iter().all(|v| matches!(v, Value::Number(_))) { let numbers: Vec = vals .into_iter() @@ -449,7 +450,7 @@ impl<'a> VM<'a> { _ => unreachable!(), }) .collect(); - Ok(Value::NumberList(Box::new(numbers))) + Ok(Value::NumberList(Rc::new(numbers))) } else { Err(VMError::RuntimeError( "values() only works when all values are the same type (String or Number)".to_string(), diff --git a/src/vm/vm.rs b/src/vm/vm.rs index 36487d0..ce8d5aa 100644 --- a/src/vm/vm.rs +++ b/src/vm/vm.rs @@ -1,4 +1,5 @@ use crate::{ast::value::Value, bytecode::BytecodeReader, opcodes::OpCodeByte}; +use std::rc::Rc; use micromap::Map; use rust_decimal::{Decimal, MathematicalOps}; use rustc_hash::FxHashMap; @@ -704,7 +705,7 @@ impl<'a> VM<'a> { let value = self.registers[val].clone(); match &mut self.registers[obj] { Value::Object(map) => { - map.insert(SmolStr::from(prop), value); + Rc::make_mut(map).insert(SmolStr::from(prop), value); } other => { return Err(VMError::RuntimeError(format!( diff --git a/tests/data_driven_tests.rs b/tests/data_driven_tests.rs index b4e1201..48d4eff 100644 --- a/tests/data_driven_tests.rs +++ b/tests/data_driven_tests.rs @@ -3,6 +3,7 @@ use indexmap::IndexMap; use rust_decimal::Decimal; use smol_str::SmolStr; use std::collections::HashMap; +use std::rc::Rc; use std::str::FromStr; #[derive(serde::Deserialize)] @@ -55,7 +56,7 @@ fn value_def_to_value(def: &ValueDef) -> Value { }; map.insert(SmolStr::from(k.as_str()), val); } - Value::Object(Box::new(map)) + Value::Object(Rc::new(map)) } let obj = def.value.as_ref().unwrap().as_object().unwrap(); json_obj_to_value(obj) diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index f2445db..6dc64b8 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -3,6 +3,7 @@ use indexmap::IndexMap; use rust_decimal::prelude::ToPrimitive; use rust_decimal_macros::dec; use smol_str::SmolStr; +use std::rc::Rc; /// Helper to run code and get the value of "result" variable fn run_and_get_result(code: &str) -> Value { @@ -628,7 +629,7 @@ fn test_numberlist_contains() { let mut compiler = Compiler::new(); let bytecode = compiler.compile(ast).expect("compile"); let mut vm = VM::new(&bytecode); - vm.set_global("nums", Value::NumberList(Box::new(vec![dec!(1), dec!(2), dec!(3), dec!(4)]))); + vm.set_global("nums", Value::NumberList(Rc::new(vec![dec!(1), dec!(2), dec!(3), dec!(4)]))); vm.execute().expect("execute"); assert_eq!(vm.get_global("result").unwrap(), &Value::Boolean(true)); } @@ -639,7 +640,7 @@ fn test_numberlist_indexof() { let mut compiler = Compiler::new(); let bytecode = compiler.compile(ast).expect("compile"); let mut vm = VM::new(&bytecode); - vm.set_global("nums", Value::NumberList(Box::new(vec![dec!(1), dec!(2), dec!(3)]))); + vm.set_global("nums", Value::NumberList(Rc::new(vec![dec!(1), dec!(2), dec!(3)]))); vm.execute().expect("execute"); assert_eq!(vm.get_global("result").unwrap(), &Value::Number(dec!(2))); } @@ -650,7 +651,7 @@ fn test_numberlist_slice() { let mut compiler = Compiler::new(); let bytecode = compiler.compile(ast).expect("compile"); let mut vm = VM::new(&bytecode); - vm.set_global("nums", Value::NumberList(Box::new(vec![dec!(10), dec!(20), dec!(30), dec!(40)]))); + vm.set_global("nums", Value::NumberList(Rc::new(vec![dec!(10), dec!(20), dec!(30), dec!(40)]))); vm.execute().expect("execute"); // slice(1,3) = [20, 30], sum = 50 assert_eq!(vm.get_global("result").unwrap(), &Value::Number(dec!(50))); @@ -662,7 +663,7 @@ fn test_numberlist_reverse() { let mut compiler = Compiler::new(); let bytecode = compiler.compile(ast).expect("compile"); let mut vm = VM::new(&bytecode); - vm.set_global("nums", Value::NumberList(Box::new(vec![dec!(1), dec!(2), dec!(3)]))); + vm.set_global("nums", Value::NumberList(Rc::new(vec![dec!(1), dec!(2), dec!(3)]))); vm.execute().expect("execute"); assert_eq!(vm.get_global("result").unwrap(), &Value::Number(dec!(3))); } @@ -673,7 +674,7 @@ fn test_numberlist_sort() { let mut compiler = Compiler::new(); let bytecode = compiler.compile(ast).expect("compile"); let mut vm = VM::new(&bytecode); - vm.set_global("nums", Value::NumberList(Box::new(vec![dec!(30), dec!(10), dec!(20)]))); + vm.set_global("nums", Value::NumberList(Rc::new(vec![dec!(30), dec!(10), dec!(20)]))); vm.execute().expect("execute"); assert_eq!(vm.get_global("result").unwrap(), &Value::Number(dec!(10))); } @@ -684,7 +685,7 @@ fn test_numberlist_isempty() { let mut compiler = Compiler::new(); let bytecode = compiler.compile(ast).expect("compile"); let mut vm = VM::new(&bytecode); - vm.set_global("nums", Value::NumberList(Box::new(vec![]))); + vm.set_global("nums", Value::NumberList(Rc::new(vec![]))); vm.execute().expect("execute"); assert_eq!(vm.get_global("result").unwrap(), &Value::Boolean(true)); } @@ -1028,7 +1029,7 @@ fn make_object(entries: Vec<(&str, Value)>) -> Value { for (k, v) in entries { map.insert(SmolStr::new(k), v); } - Value::Object(Box::new(map)) + Value::Object(Rc::new(map)) } #[test] @@ -1113,7 +1114,7 @@ fn test_object_method_keys() { let result = run_expr_with_globals("person.keys()", vec![("person", obj)]); assert_eq!( result, - Value::StringList(Box::new(vec![SmolStr::new("name"), SmolStr::new("age")])) + Value::StringList(Rc::new(vec![SmolStr::new("name"), SmolStr::new("age")])) ); } @@ -1177,7 +1178,7 @@ fn test_object_method_values_strings() { let result = run_expr_with_globals("obj.values()", vec![("obj", obj)]); assert_eq!( result, - Value::StringList(Box::new(vec![SmolStr::new("x"), SmolStr::new("y")])) + Value::StringList(Rc::new(vec![SmolStr::new("x"), SmolStr::new("y")])) ); } @@ -1188,7 +1189,7 @@ fn test_object_method_values_numbers() { ("b", Value::Number(dec!(2))), ]); let result = run_expr_with_globals("obj.values()", vec![("obj", obj)]); - assert_eq!(result, Value::NumberList(Box::new(vec![dec!(1), dec!(2)]))); + assert_eq!(result, Value::NumberList(Rc::new(vec![dec!(1), dec!(2)]))); } #[test]