Feat: script execution performance optimization

This commit is contained in:
2026-02-22 14:16:38 +01:00
parent 3d552ec072
commit 81f475a75b
20 changed files with 377 additions and 134 deletions

View File

@@ -76,7 +76,6 @@ pub type VariablesMap = HashMap<String, Value>;
pub type Variables = Arc<ArcSwap<VariablesMap>>;
pub type Dictionary = Arc<Mutex<HashMap<String, Vec<Op>>>>;
pub type Rng = Arc<Mutex<StdRng>>;
pub type Stack = Mutex<Vec<Value>>;
pub(super) type CmdSnapshot<'a> = (Option<&'a Value>, &'a [(&'static str, Value)]);
#[derive(Clone, Debug)]

View File

@@ -1,6 +1,5 @@
//! Stack-based Forth interpreter with audio command generation.
use parking_lot::Mutex;
use rand::rngs::StdRng;
use rand::{Rng as RngTrait, SeedableRng};
use std::borrow::Cow;
@@ -10,43 +9,45 @@ use std::sync::Arc;
use super::compiler::compile_script;
use super::ops::Op;
use super::types::{
CmdRegister, Dictionary, ExecutionTrace, ResolvedValue, Rng, SourceSpan, Stack, StepContext,
Value, Variables, VariablesMap,
CmdRegister, Dictionary, ExecutionTrace, ResolvedValue, Rng, SourceSpan, StepContext, Value,
Variables, VariablesMap,
};
pub struct Forth {
stack: Stack,
stack: Vec<Value>,
vars: Variables,
dict: Dictionary,
rng: Rng,
cache: HashMap<String, Arc<[Op]>>,
}
impl Forth {
pub fn new(vars: Variables, dict: Dictionary, rng: Rng) -> Self {
Self {
stack: Mutex::new(Vec::new()),
stack: Vec::new(),
vars,
dict,
rng,
cache: HashMap::new(),
}
}
pub fn stack(&self) -> Vec<Value> {
self.stack.lock().clone()
self.stack.clone()
}
pub fn clear_stack(&self) {
self.stack.lock().clear();
pub fn clear_stack(&mut self) {
self.stack.clear();
}
pub fn evaluate(&self, script: &str, ctx: &StepContext) -> Result<Vec<String>, String> {
pub fn evaluate(&mut self, script: &str, ctx: &StepContext) -> Result<Vec<String>, String> {
let (outputs, var_writes) = self.evaluate_impl(script, ctx, None)?;
self.apply_var_writes(var_writes);
Ok(outputs)
}
pub fn evaluate_with_trace(
&self,
&mut self,
script: &str,
ctx: &StepContext,
trace: &mut ExecutionTrace,
@@ -57,7 +58,7 @@ impl Forth {
}
pub fn evaluate_raw(
&self,
&mut self,
script: &str,
ctx: &StepContext,
trace: &mut ExecutionTrace,
@@ -77,7 +78,7 @@ impl Forth {
}
fn evaluate_impl(
&self,
&mut self,
script: &str,
ctx: &StepContext,
trace: Option<&mut ExecutionTrace>,
@@ -86,26 +87,35 @@ impl Forth {
return Err("empty script".into());
}
let ops = compile_script(script, &self.dict)?;
let ops = if let Some(cached) = self.cache.get(script) {
Arc::clone(cached)
} else {
let compiled = compile_script(script, &self.dict)?;
let ops: Arc<[Op]> = Arc::from(compiled);
self.cache.insert(script.to_owned(), Arc::clone(&ops));
ops
};
self.execute(&ops, ctx, trace)
}
fn execute(
&self,
&mut self,
ops: &[Op],
ctx: &StepContext,
trace: Option<&mut ExecutionTrace>,
) -> Result<(Vec<String>, HashMap<String, Value>), String> {
let mut stack = self.stack.lock();
let mut outputs: Vec<String> = Vec::with_capacity(8);
let mut cmd = CmdRegister::new();
let vars_snapshot = self.vars.load_full();
let mut var_writes: HashMap<String, Value> = HashMap::new();
let mut var_writes: Vec<(String, Value)> = Vec::new();
self.execute_ops(
Self::execute_ops(
&self.rng,
&self.dict,
&mut self.cache,
ops,
ctx,
&mut stack,
&mut self.stack,
&mut outputs,
&mut cmd,
trace,
@@ -113,13 +123,15 @@ impl Forth {
&mut var_writes,
)?;
Ok((outputs, var_writes))
let var_map: HashMap<String, Value> = var_writes.into_iter().collect();
Ok((outputs, var_map))
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::only_used_in_recursion)]
fn execute_ops(
&self,
rng: &Rng,
dict: &Dictionary,
cache: &mut HashMap<String, Arc<[Op]>>,
ops: &[Op],
ctx: &StepContext,
stack: &mut Vec<Value>,
@@ -127,11 +139,14 @@ impl Forth {
cmd: &mut CmdRegister,
trace: Option<&mut ExecutionTrace>,
vars_snapshot: &VariablesMap,
var_writes: &mut HashMap<String, Value>,
var_writes: &mut Vec<(String, Value)>,
) -> Result<(), String> {
let mut pc = 0;
let trace_cell = std::cell::RefCell::new(trace);
let var_writes_cell = std::cell::RefCell::new(Some(var_writes));
let rng_ref = rng;
let dict_ref = dict;
let cache_cell = std::cell::RefCell::new(Some(cache));
let run_quotation = |quot: Value,
stack: &mut Vec<Value>,
@@ -148,7 +163,12 @@ impl Forth {
let mut trace_opt = trace_cell.borrow_mut().take();
let mut var_writes_guard = var_writes_cell.borrow_mut();
let vw = var_writes_guard.as_mut().expect("var_writes taken");
self.execute_ops(
let mut cache_guard = cache_cell.borrow_mut();
let c = cache_guard.as_mut().expect("cache taken");
Self::execute_ops(
rng_ref,
dict_ref,
c,
&quot_ops,
ctx,
stack,
@@ -158,6 +178,7 @@ impl Forth {
vars_snapshot,
vw,
)?;
drop(cache_guard);
drop(var_writes_guard);
*trace_cell.borrow_mut() = trace_opt;
Ok(())
@@ -355,9 +376,9 @@ impl Forth {
ensure(stack, count)?;
let start = stack.len() - count;
let slice = &mut stack[start..];
let mut rng = self.rng.lock();
let mut rng_guard = rng.lock();
for i in (1..slice.len()).rev() {
let j = rng.gen_range(0..=i);
let j = rng_guard.gen_range(0..=i);
slice.swap(i, j);
}
}
@@ -620,7 +641,10 @@ impl Forth {
let vw = var_writes_cell.borrow();
let vw_ref = vw.as_ref().expect("var_writes taken");
let val = vw_ref
.get(name)
.iter()
.rev()
.find(|(k, _)| k == name)
.map(|(_, v)| v)
.or_else(|| vars_snapshot.get(name))
.cloned()
.unwrap_or(Value::Int(0, None));
@@ -635,7 +659,7 @@ impl Forth {
.borrow_mut()
.as_mut()
.expect("var_writes taken")
.insert(name, val);
.push((name, val));
}
Op::SetKeep => {
let name = pop(stack)?;
@@ -645,7 +669,7 @@ impl Forth {
.borrow_mut()
.as_mut()
.expect("var_writes taken")
.insert(name, val);
.push((name, val));
}
Op::GetContext(name) => {
@@ -680,7 +704,7 @@ impl Forth {
} else {
(*b_i, *a_i)
};
let val = self.rng.lock().gen_range(lo..=hi);
let val = rng.lock().gen_range(lo..=hi);
record_resolved(&trace_cell, *word_span, ResolvedValue::Int(val));
stack.push(Value::Int(val, None));
}
@@ -691,7 +715,7 @@ impl Forth {
let val = if (hi - lo).abs() < f64::EPSILON {
lo
} else {
self.rng.lock().gen_range(lo..hi)
rng.lock().gen_range(lo..hi)
};
record_resolved(&trace_cell, *word_span, ResolvedValue::Float(val));
stack.push(Value::Float(val, None));
@@ -705,7 +729,7 @@ impl Forth {
return Err("exprand requires positive values".into());
}
let (lo, hi) = if lo <= hi { (lo, hi) } else { (hi, lo) };
let u: f64 = self.rng.lock().gen();
let u: f64 = rng.lock().gen();
let val = lo * (hi / lo).powf(u);
record_resolved(&trace_cell, *word_span, ResolvedValue::Float(val));
stack.push(Value::Float(val, None));
@@ -717,14 +741,14 @@ impl Forth {
return Err("logrand requires positive values".into());
}
let (lo, hi) = if lo <= hi { (lo, hi) } else { (hi, lo) };
let u: f64 = self.rng.lock().gen();
let u: f64 = rng.lock().gen();
let val = hi * (lo / hi).powf(u);
record_resolved(&trace_cell, *word_span, ResolvedValue::Float(val));
stack.push(Value::Float(val, None));
}
Op::Seed => {
let s = pop_int(stack)?;
*self.rng.lock() = StdRng::seed_from_u64(s as u64);
*rng.lock() = StdRng::seed_from_u64(s as u64);
}
Op::Cycle(word_span) | Op::PCycle(word_span) => {
@@ -751,7 +775,7 @@ impl Forth {
if count == 0 {
return Err("choose count must be > 0".into());
}
let idx = self.rng.lock().gen_range(0..count);
let idx = rng.lock().gen_range(0..count);
if let Some(span) = word_span {
if stack.len() >= count {
let start = stack.len() - count;
@@ -808,7 +832,7 @@ impl Forth {
if total <= 0.0 {
return Err("wchoose: total weight must be > 0".into());
}
let threshold: f64 = self.rng.lock().gen::<f64>() * total;
let threshold: f64 = rng.lock().gen::<f64>() * total;
let mut cumulative = 0.0;
let mut selected_idx = count - 1;
for (i, &w) in weights.iter().enumerate() {
@@ -826,7 +850,7 @@ impl Forth {
Op::ChanceExec(word_span) | Op::ProbExec(word_span) => {
let threshold = pop_float(stack)?;
let quot = pop(stack)?;
let val: f64 = self.rng.lock().gen();
let val: f64 = rng.lock().gen();
let limit = match &ops[pc] {
Op::ChanceExec(_) => threshold,
_ => threshold / 100.0,
@@ -839,7 +863,7 @@ impl Forth {
}
Op::Coin(word_span) => {
let val: f64 = self.rng.lock().gen();
let val: f64 = rng.lock().gen();
let result = val < 0.5;
record_resolved(&trace_cell, *word_span, ResolvedValue::Bool(result));
stack.push(Value::Int(if result { 1 } else { 0 }, None));
@@ -979,7 +1003,7 @@ impl Forth {
.borrow_mut()
.as_mut()
.expect("var_writes taken")
.insert("__tempo__".to_string(), Value::Float(clamped, None));
.push(("__tempo__".to_string(), Value::Float(clamped, None)));
}
Op::SetSpeed => {
@@ -989,7 +1013,7 @@ impl Forth {
.borrow_mut()
.as_mut()
.expect("var_writes taken")
.insert(ctx.speed_key.to_string(), Value::Float(clamped, None));
.push((ctx.speed_key.to_string(), Value::Float(clamped, None)));
}
Op::Loop => {
@@ -1129,7 +1153,7 @@ impl Forth {
.borrow_mut()
.as_mut()
.expect("var_writes taken")
.insert("i".to_string(), Value::Int(i, None));
.push(("i".to_string(), Value::Int(i, None)));
run_quotation(quot.clone(), stack, outputs, cmd)?;
}
}
@@ -1365,7 +1389,12 @@ impl Forth {
}
Op::Forget => {
let name = pop(stack)?;
self.dict.lock().remove(name.as_str()?);
dict.lock().remove(name.as_str()?);
cache_cell
.borrow_mut()
.as_mut()
.expect("cache taken")
.clear();
}
}
pc += 1;