Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2024-03-12 - [Context Map Lookups]
**Learning:** Found an edge case where map access and function execution was holding a Mutex lock which leads to high lock contention, especially since Context instances store closures that might be arbitrarily expensive. Double map lookups in `.value()` (`is_none()` then `unwrap()`) combined with lock-holding was a bottleneck for expression execution.
**Action:** Always fetch the target value into an owned variable outside the lock block (using scope bounds or `cloned()`) in high-frequency map lookups. Never execute user closures inside a lock guard.
2 changes: 1 addition & 1 deletion benches/display_expression.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use criterion::{criterion_group, criterion_main, Criterion, black_box};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use expression_engine::{parse_expression, ExprAST};

fn bench_display_expression(c: &mut Criterion) {
Expand Down
28 changes: 12 additions & 16 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,36 +31,32 @@ impl Context {
}

pub fn get_func(&self, name: &str) -> Option<Arc<InnerFunction>> {
let value = self.get(name)?;
match value {
ContextValue::Function(func) => Some(func.clone()),
match self.get(name)? {
ContextValue::Function(func) => Some(func),
ContextValue::Variable(_) => None,
}
}

pub fn get_variable(&self, name: &str) -> Option<Value> {
let value = self.get(name)?;
match value {
ContextValue::Variable(v) => Some(v.clone()),
match self.get(name)? {
ContextValue::Variable(v) => Some(v),
ContextValue::Function(_) => None,
}
}

pub fn get(&self, name: &str) -> Option<ContextValue> {
let binding = self.0.lock().unwrap();
let value = binding.get(name)?;
Some(value.clone())
self.0.lock().unwrap().get(name).cloned()
}

pub fn value(&self, name: &str) -> Result<Value> {
let binding = self.0.lock().unwrap();
if binding.get(name).is_none() {
return Ok(Value::None);
}
let value = binding.get(name).unwrap();
let value = {
let binding = self.0.lock().unwrap();
binding.get(name).cloned()
};
match value {
ContextValue::Variable(v) => Ok(v.clone()),
ContextValue::Function(func) => func(Vec::new()),
Some(ContextValue::Variable(v)) => Ok(v),
Some(ContextValue::Function(func)) => func(Vec::new()),
None => Ok(Value::None),
}
}
}
Expand Down
4 changes: 1 addition & 3 deletions src/descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,7 @@ impl DescriptorManager {
fn get(&self, key: DescriptorKey) -> Option<Descriptor> {
let binding = self.store.lock().unwrap();
let value = binding.get(&key);
if value.is_none() {
return None;
}
value?;
Some(value.unwrap().clone())
Comment on lines 59 to 61
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While the use of the ? operator is an improvement, this can be made even more concise and idiomatic by using .cloned(). This avoids the pattern of checking for None and then unwrapping, which aligns well with the other optimizations in this PR.

Suggested change
let value = binding.get(&key);
if value.is_none() {
return None;
}
value?;
Some(value.unwrap().clone())
binding.get(&key).cloned()

}

Expand Down
2 changes: 1 addition & 1 deletion src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ impl InnerFunctionManager {
pub fn new() -> Self {
static STORE: OnceCell<Mutex<HashMap<String, Arc<InnerFunction>>>> = OnceCell::new();
let store = STORE.get_or_init(|| Mutex::new(HashMap::new()));
InnerFunctionManager { store: store }
InnerFunctionManager { store }
}

pub fn init(&mut self) {
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ pub fn execute(expr: &str, mut ctx: context::Context) -> Result<Value> {
/// let ast = parse_expression(input);
/// assert!(ast.is_ok());
/// ```
pub fn parse_expression(expr: &str) -> Result<ExprAST> {
pub fn parse_expression(expr: &str) -> Result<ExprAST<'_>> {
init();
parser::Parser::new(expr)?.parse_stmt()
}
Expand Down
22 changes: 11 additions & 11 deletions src/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@ impl InfixOpManager {
pub fn new() -> Self {
static STORE: OnceCell<Mutex<HashMap<String, InfixOpConfig>>> = OnceCell::new();
let store = STORE.get_or_init(|| Mutex::new(HashMap::new()));
InfixOpManager { store: store }
InfixOpManager { store }
}

pub fn init(&mut self) {
use InfixOpAssociativity::*;
use InfixOpType::*;
self.register("=", 20, SETTER, RIGHT, Arc::new(|_, right| Ok(right)));

for op in vec!["+=", "-=", "*=", "/=", "%="] {
for op in ["+=", "-=", "*=", "/=", "%="] {
self.register(
op,
20,
Expand Down Expand Up @@ -88,7 +88,7 @@ impl InfixOpManager {
);
}

for op in vec!["<<=", ">>=", "&=", "^=", "|="] {
for op in ["<<=", ">>=", "&=", "^=", "|="] {
self.register(
op,
20,
Expand All @@ -109,7 +109,7 @@ impl InfixOpManager {
);
}

for (op, precedence) in vec![("||", 40), ("&&", 50)] {
for (op, precedence) in [("||", 40), ("&&", 50)] {
self.register(
op,
precedence,
Expand All @@ -127,7 +127,7 @@ impl InfixOpManager {
);
}

for op in vec!["<", "<=", ">", ">="] {
for op in ["<", "<=", ">", ">="] {
self.register(
op,
60,
Expand All @@ -148,7 +148,7 @@ impl InfixOpManager {
);
}

for op in vec!["==", "!="] {
for op in ["==", "!="] {
self.register(
op,
60,
Expand All @@ -166,7 +166,7 @@ impl InfixOpManager {
);
}

for (op, precedence) in vec![("|", 70), ("^", 80), ("&", 90), ("<<", 100), (">>", 100)] {
for (op, precedence) in [("|", 70), ("^", 80), ("&", 90), ("<<", 100), (">>", 100)] {
self.register(
op,
precedence,
Expand All @@ -187,7 +187,7 @@ impl InfixOpManager {
);
}

for (op, precedence) in vec![("+", 110), ("-", 110), ("*", 120), ("/", 120), ("%", 120)] {
for (op, precedence) in [("+", 110), ("-", 110), ("*", 120), ("/", 120), ("%", 120)] {
self.register(
op,
precedence,
Expand Down Expand Up @@ -308,7 +308,7 @@ impl InfixOpManager {
let mut ans = vec![];
let binding = self.store.lock().unwrap();
for (op, InfixOpConfig(precedence, _, _, _)) in binding.iter() {
ans.push((op.clone(), precedence.clone()));
ans.push((op.clone(), *precedence));
}
ans.sort_by(|a, b| a.1.cmp(&b.1));
ans
Expand All @@ -324,7 +324,7 @@ impl PrefixOpManager {
pub fn new() -> Self {
static STORE: OnceCell<Mutex<HashMap<String, Arc<PrefixOpFunc>>>> = OnceCell::new();
let store = STORE.get_or_init(|| Mutex::new(HashMap::new()));
PrefixOpManager { store: store }
PrefixOpManager { store }
}

pub fn init(&mut self) {
Expand Down Expand Up @@ -422,7 +422,7 @@ impl PostfixOpManager {
pub fn new() -> Self {
static STORE: OnceCell<Mutex<HashMap<String, Arc<PrefixOpFunc>>>> = OnceCell::new();
let store = STORE.get_or_init(|| Mutex::new(HashMap::new()));
Self { store: store }
Self { store }
}

pub fn init(&mut self) {
Expand Down
58 changes: 25 additions & 33 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,22 +52,16 @@ impl<'a> fmt::Display for ExprAST<'a> {
Self::Unary(op, rhs) => {
write!(f, "Unary AST: Op: {}, Rhs: {}", op, rhs)
}
Self::Binary(op, lhs, rhs) => write!(
f,
"Binary AST: Op: {}, Lhs: {}, Rhs: {}",
op,
lhs,
rhs
),
Self::Binary(op, lhs, rhs) => {
write!(f, "Binary AST: Op: {}, Lhs: {}, Rhs: {}", op, lhs, rhs)
}
Self::Postfix(lhs, op) => {
write!(f, "Postfix AST: Lhs: {}, Op: {}", lhs, op,)
}
Self::Ternary(condition, lhs, rhs) => write!(
f,
"Ternary AST: Condition: {}, Lhs: {}, Rhs: {}",
condition,
lhs,
rhs
condition, lhs, rhs
),
Self::Reference(name) => write!(f, "Reference AST: reference: {}", name),
Self::Function(name, params) => {
Expand Down Expand Up @@ -154,7 +148,7 @@ impl<'a> ExprAST<'a> {
}

fn exec_unary(&self, op: &'a str, rhs: &ExprAST, ctx: &mut Context) -> Result<Value> {
PrefixOpManager::new().get(&op)?(rhs.exec(ctx)?)
PrefixOpManager::new().get(op)?(rhs.exec(ctx)?)
}

fn exec_binary(
Expand All @@ -164,15 +158,15 @@ impl<'a> ExprAST<'a> {
rhs: &ExprAST<'a>,
ctx: &mut Context,
) -> Result<Value> {
match InfixOpManager::new().get_op_type(&op)? {
match InfixOpManager::new().get_op_type(op)? {
InfixOpType::CALC => {
InfixOpManager::new().get_handler(&op)?(lhs.exec(ctx)?, rhs.exec(ctx)?)
InfixOpManager::new().get_handler(op)?(lhs.exec(ctx)?, rhs.exec(ctx)?)
}
InfixOpType::SETTER => {
let (a, b) = (lhs.exec(ctx)?, rhs.exec(ctx)?);
ctx.set_variable(
lhs.get_reference_name()?,
InfixOpManager::new().get_handler(&op)?(a, b)?,
InfixOpManager::new().get_handler(op)?(a, b)?,
);
Ok(Value::None)
}
Expand Down Expand Up @@ -268,7 +262,7 @@ impl<'a> ExprAST<'a> {
"false".into()
}
}
String(value) => "\"".to_string() + &value + "\"",
String(value) => "\"".to_string() + value + "\"",
}
}

Expand Down Expand Up @@ -298,15 +292,15 @@ impl<'a> ExprAST<'a> {
let (is, precidence) = lhs.get_precidence();
let mut tmp: String = lhs.expr();
if is && precidence < InfixOpManager::new().get_precidence(op) {
tmp = "(".to_string() + &lhs.expr() + &")".to_string();
tmp = "(".to_string() + &lhs.expr() + ")";
}
tmp
};
let right = {
let (is, precidence) = rhs.get_precidence();
let mut tmp = rhs.expr();
if is && precidence < InfixOpManager::new().get_precidence(op) {
tmp = "(".to_string() + &rhs.expr() + &")".to_string();
tmp = "(".to_string() + &rhs.expr() + ")";
}
tmp
};
Expand All @@ -326,10 +320,10 @@ impl<'a> ExprAST<'a> {
for i in 0..params.len() {
s.push_str(params[i].expr().as_str());
if i < params.len() - 1 {
s.push_str(",");
s.push(',');
}
}
s.push_str("]");
s.push(']');
s
}

Expand All @@ -338,13 +332,13 @@ impl<'a> ExprAST<'a> {
for i in 0..m.len() {
let (key, value) = m[i].clone();
s.push_str(key.expr().as_str());
s.push_str(":");
s.push(':');
s.push_str(value.expr().as_str());
if i < m.len() - 1 {
s.push_str(",");
s.push(',');
}
}
s.push_str("}");
s.push('}');
s
}

Expand All @@ -353,7 +347,7 @@ impl<'a> ExprAST<'a> {
for i in 0..exprs.len() {
s.push_str(exprs[i].expr().as_str());
if i < exprs.len() - 1 {
s.push_str(";");
s.push(';');
}
}
s
Expand All @@ -379,25 +373,25 @@ impl<'a> ExprAST<'a> {
op.clone(),
),
Self::List(values) => DescriptorManager::new().get_list_descriptor()(
values.into_iter().map(|v| v.describe()).collect(),
values.iter().map(|v| v.describe()).collect(),
),
Self::Map(values) => DescriptorManager::new().get_map_descriptor()(
values
.into_iter()
.iter()
.map(|value| (value.0.describe(), value.1.describe()))
.collect(),
),
Self::Function(name, values) => DescriptorManager::new()
.get_function_descriptor(name.to_string())(
name.to_string(),
values.into_iter().map(|v| v.describe()).collect(),
values.iter().map(|v| v.describe()).collect(),
),
Self::Reference(name) => DescriptorManager::new()
.get_reference_descriptor(name.to_string())(
name.to_string()
),
Self::Stmt(values) => DescriptorManager::new().get_chain_descriptor()(
values.into_iter().map(|v| v.describe()).collect(),
values.iter().map(|v| v.describe()).collect(),
),
Self::Ternary(condition, lhs, rhs) => {
DescriptorManager::new().get_ternary_descriptor()(
Expand All @@ -416,23 +410,21 @@ pub struct Parser<'a> {
}

impl<'a> Parser<'a> {
fn cur_tok(&self) -> Token {
self.tokenizer.cur_token.clone()
fn cur_tok(&self) -> Token<'_> {
self.tokenizer.cur_token
}

pub fn new(input: &'a str) -> Result<Self> {
let mut tokenizer = Tokenizer::new(input);
tokenizer.next()?;
Ok(Self {
tokenizer: tokenizer,
})
Ok(Self { tokenizer })
}

fn is_eof(&self) -> bool {
self.cur_tok().is_eof()
}

pub fn next(&mut self) -> Result<Token> {
pub fn next(&mut self) -> Result<Token<'_>> {
self.tokenizer.next()
}

Expand Down
2 changes: 1 addition & 1 deletion src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ pub fn check_op(token: Token, expected: &str) -> bool {
}
_ => return false,
}
return false;
false
}

impl<'input> Token<'input> {
Expand Down
Loading
Loading