perf(vm): 字符串拼接用 push_to_string 消去中间分配, GC阈值 8M→32M

- oxide_runtime_api 新增 push_to_string: 直接将 JsValue 写入 String buffer
- dispatch_add / dispatch_compound_add 复用 Vm::string_buf, 每次concat从4次分配降到1次
- vm_support 新增 new_string_owned 接受 String 转移所有权
- kernel session_gc_threshold 8MB→32MB 减少频繁 GC
- build.sh: make clean 清跨平台 .obj 残留

(cherry picked from commit ad7b044a5a24b23819d783c5419d3e33f936dfc5)
This commit is contained in:
爱特每羊队
2026-06-29 14:04:50 +08:00
parent 6edeadc149
commit 5ecb428320
6 changed files with 76 additions and 13 deletions
+12 -8
View File
@@ -10,10 +10,12 @@ impl Vm {
let lhs = self.coerce_primitive_bounded(self.regs[a], false)?;
let rhs = self.coerce_primitive_bounded(self.regs[b], false)?;
if lhs.is_string() || rhs.is_string() {
let ls = coercion::to_string(lhs);
let rs = coercion::to_string(rhs);
let concat = format!("{ls}{rs}");
self.regs[rd] = self.new_string(&concat);
let mut buf = std::mem::take(&mut self.string_buf);
buf.clear();
coercion::push_to_string(lhs, &mut buf);
coercion::push_to_string(rhs, &mut buf);
self.regs[rd] = self.new_string(&buf);
self.string_buf = buf;
} else {
let ln = coercion::to_number(lhs);
let rn = coercion::to_number(rhs);
@@ -43,10 +45,12 @@ impl Vm {
let lhs = self.coerce_primitive_bounded(self.regs[rd], false)?;
let rhs = self.coerce_primitive_bounded(self.regs[a], false)?;
if lhs.is_string() || rhs.is_string() {
let ls = coercion::to_string(lhs);
let rs = coercion::to_string(rhs);
let concat = format!("{ls}{rs}");
self.regs[rd] = self.new_string(&concat);
let mut buf = std::mem::take(&mut self.string_buf);
buf.clear();
coercion::push_to_string(lhs, &mut buf);
coercion::push_to_string(rhs, &mut buf);
self.regs[rd] = self.new_string(&buf);
self.string_buf = buf;
} else {
let ln = coercion::to_number(lhs);
let rn = coercion::to_number(rhs);
+2
View File
@@ -189,6 +189,8 @@ pub struct Vm {
pub(crate) iters: IterState,
/// Grouped inline-cache and instruction counters.
pub(crate) profiling: ProfilingState,
/// Reusable string buffer for concatenation to avoid allocation per `+` op.
pub(crate) string_buf: String,
}
impl Vm {
+10 -2
View File
@@ -69,6 +69,7 @@ impl Vm {
ic_misses: std::cell::Cell::new(0),
instruction_count: 0,
},
string_buf: String::new(),
};
vm_info!("Vm created");
vm
@@ -127,6 +128,7 @@ impl Vm {
ic_misses: std::cell::Cell::new(0),
instruction_count: 0,
},
string_buf: String::new(),
};
vm_info!("Vm created (pool)");
vm
@@ -220,9 +222,15 @@ impl Vm {
}
pub fn new_string(&mut self, s: &str) -> JsValue {
let ptr = Box::into_raw(Box::new(JsString::new(s.to_string())));
self.new_string_owned(s.to_string())
}
/// Same as `new_string` but takes `String` by move, avoiding a clone.
pub fn new_string_owned(&mut self, s: String) -> JsValue {
let len = s.len();
let ptr = Box::into_raw(Box::new(JsString::new(s)));
self.gc_state.session_string_ptrs.push(ptr);
self.gc_state.session_bytes_allocated += std::mem::size_of::<JsString>() + s.len();
self.gc_state.session_bytes_allocated += std::mem::size_of::<JsString>() + len;
JsValue::string(ptr)
}