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:
@@ -76,6 +76,7 @@ build_quickjs() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
info "编译 QuickJS..."
|
info "编译 QuickJS..."
|
||||||
|
make clean 2>&1 | tail -1 # 清理跨平台残留 .obj,避免 PE/ELF 混用
|
||||||
if make -j"$(nproc 2>/dev/null || echo 4)" all 2>&1; then
|
if make -j"$(nproc 2>/dev/null || echo 4)" all 2>&1; then
|
||||||
info "QuickJS 编译成功"
|
info "QuickJS 编译成功"
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ impl KernelConfig {
|
|||||||
max_dead_strings: Some(10_000),
|
max_dead_strings: Some(10_000),
|
||||||
max_steps: None,
|
max_steps: None,
|
||||||
max_call_depth: 1024,
|
max_call_depth: 1024,
|
||||||
session_gc_threshold: 8_388_608,
|
session_gc_threshold: 33_554_432,
|
||||||
max_cached_modules: 512,
|
max_cached_modules: 512,
|
||||||
log_levels: [Level::Off; SUBSYSTEM_COUNT],
|
log_levels: [Level::Off; SUBSYSTEM_COUNT],
|
||||||
warmup_builtin_shapes: true,
|
warmup_builtin_shapes: true,
|
||||||
@@ -55,7 +55,7 @@ impl KernelConfig {
|
|||||||
max_dead_strings: Some(10_000),
|
max_dead_strings: Some(10_000),
|
||||||
max_steps: None,
|
max_steps: None,
|
||||||
max_call_depth: 1024,
|
max_call_depth: 1024,
|
||||||
session_gc_threshold: 8_388_608,
|
session_gc_threshold: 33_554_432,
|
||||||
max_cached_modules: 512,
|
max_cached_modules: 512,
|
||||||
log_levels: [Level::Off; SUBSYSTEM_COUNT],
|
log_levels: [Level::Off; SUBSYSTEM_COUNT],
|
||||||
warmup_builtin_shapes: true,
|
warmup_builtin_shapes: true,
|
||||||
@@ -71,7 +71,7 @@ impl KernelConfig {
|
|||||||
max_dead_strings: Some(5_000),
|
max_dead_strings: Some(5_000),
|
||||||
max_steps: None,
|
max_steps: None,
|
||||||
max_call_depth: 1024,
|
max_call_depth: 1024,
|
||||||
session_gc_threshold: 8_388_608,
|
session_gc_threshold: 33_554_432,
|
||||||
max_cached_modules: 512,
|
max_cached_modules: 512,
|
||||||
log_levels: [Level::Off; SUBSYSTEM_COUNT],
|
log_levels: [Level::Off; SUBSYSTEM_COUNT],
|
||||||
warmup_builtin_shapes: true,
|
warmup_builtin_shapes: true,
|
||||||
|
|||||||
@@ -193,6 +193,54 @@ pub fn to_int32(val: JsValue) -> i32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Append the string representation of `val` to `buf` without allocating
|
||||||
|
/// intermediate temporary Strings. Used in hot-path string concatenation.
|
||||||
|
pub fn push_to_string(val: JsValue, buf: &mut String) {
|
||||||
|
if val.is_int() {
|
||||||
|
use std::fmt::Write;
|
||||||
|
let _ = write!(buf, "{}", val.as_int());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if val.is_double() {
|
||||||
|
let d = val.as_double();
|
||||||
|
if d.is_nan() {
|
||||||
|
buf.push_str("NaN");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if d.is_infinite() {
|
||||||
|
buf.push_str(if d.is_sign_positive() { "Infinity" } else { "-Infinity" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if d.is_finite() && d.fract() == 0.0 {
|
||||||
|
use std::fmt::Write;
|
||||||
|
let _ = write!(buf, "{}", d as i64);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut ryubuf = ryu::Buffer::new();
|
||||||
|
buf.push_str(ryubuf.format(d));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if val.is_bool() {
|
||||||
|
buf.push_str(if val.as_bool() { "true" } else { "false" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if val.is_null() {
|
||||||
|
buf.push_str("null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if val.is_undefined() {
|
||||||
|
buf.push_str("undefined");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if val.is_string() {
|
||||||
|
unsafe { buf.push_str(string_data(val)) };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if val.is_object() {
|
||||||
|
buf.push_str("[object]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_string(val: JsValue) -> String {
|
pub fn to_string(val: JsValue) -> String {
|
||||||
if val.is_int() {
|
if val.is_int() {
|
||||||
return val.as_int().to_string();
|
return val.as_int().to_string();
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ impl Vm {
|
|||||||
let lhs = self.coerce_primitive_bounded(self.regs[a], false)?;
|
let lhs = self.coerce_primitive_bounded(self.regs[a], false)?;
|
||||||
let rhs = self.coerce_primitive_bounded(self.regs[b], false)?;
|
let rhs = self.coerce_primitive_bounded(self.regs[b], false)?;
|
||||||
if lhs.is_string() || rhs.is_string() {
|
if lhs.is_string() || rhs.is_string() {
|
||||||
let ls = coercion::to_string(lhs);
|
let mut buf = std::mem::take(&mut self.string_buf);
|
||||||
let rs = coercion::to_string(rhs);
|
buf.clear();
|
||||||
let concat = format!("{ls}{rs}");
|
coercion::push_to_string(lhs, &mut buf);
|
||||||
self.regs[rd] = self.new_string(&concat);
|
coercion::push_to_string(rhs, &mut buf);
|
||||||
|
self.regs[rd] = self.new_string(&buf);
|
||||||
|
self.string_buf = buf;
|
||||||
} else {
|
} else {
|
||||||
let ln = coercion::to_number(lhs);
|
let ln = coercion::to_number(lhs);
|
||||||
let rn = coercion::to_number(rhs);
|
let rn = coercion::to_number(rhs);
|
||||||
@@ -43,10 +45,12 @@ impl Vm {
|
|||||||
let lhs = self.coerce_primitive_bounded(self.regs[rd], false)?;
|
let lhs = self.coerce_primitive_bounded(self.regs[rd], false)?;
|
||||||
let rhs = self.coerce_primitive_bounded(self.regs[a], false)?;
|
let rhs = self.coerce_primitive_bounded(self.regs[a], false)?;
|
||||||
if lhs.is_string() || rhs.is_string() {
|
if lhs.is_string() || rhs.is_string() {
|
||||||
let ls = coercion::to_string(lhs);
|
let mut buf = std::mem::take(&mut self.string_buf);
|
||||||
let rs = coercion::to_string(rhs);
|
buf.clear();
|
||||||
let concat = format!("{ls}{rs}");
|
coercion::push_to_string(lhs, &mut buf);
|
||||||
self.regs[rd] = self.new_string(&concat);
|
coercion::push_to_string(rhs, &mut buf);
|
||||||
|
self.regs[rd] = self.new_string(&buf);
|
||||||
|
self.string_buf = buf;
|
||||||
} else {
|
} else {
|
||||||
let ln = coercion::to_number(lhs);
|
let ln = coercion::to_number(lhs);
|
||||||
let rn = coercion::to_number(rhs);
|
let rn = coercion::to_number(rhs);
|
||||||
|
|||||||
@@ -189,6 +189,8 @@ pub struct Vm {
|
|||||||
pub(crate) iters: IterState,
|
pub(crate) iters: IterState,
|
||||||
/// Grouped inline-cache and instruction counters.
|
/// Grouped inline-cache and instruction counters.
|
||||||
pub(crate) profiling: ProfilingState,
|
pub(crate) profiling: ProfilingState,
|
||||||
|
/// Reusable string buffer for concatenation to avoid allocation per `+` op.
|
||||||
|
pub(crate) string_buf: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Vm {
|
impl Vm {
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ impl Vm {
|
|||||||
ic_misses: std::cell::Cell::new(0),
|
ic_misses: std::cell::Cell::new(0),
|
||||||
instruction_count: 0,
|
instruction_count: 0,
|
||||||
},
|
},
|
||||||
|
string_buf: String::new(),
|
||||||
};
|
};
|
||||||
vm_info!("Vm created");
|
vm_info!("Vm created");
|
||||||
vm
|
vm
|
||||||
@@ -127,6 +128,7 @@ impl Vm {
|
|||||||
ic_misses: std::cell::Cell::new(0),
|
ic_misses: std::cell::Cell::new(0),
|
||||||
instruction_count: 0,
|
instruction_count: 0,
|
||||||
},
|
},
|
||||||
|
string_buf: String::new(),
|
||||||
};
|
};
|
||||||
vm_info!("Vm created (pool)");
|
vm_info!("Vm created (pool)");
|
||||||
vm
|
vm
|
||||||
@@ -220,9 +222,15 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_string(&mut self, s: &str) -> JsValue {
|
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_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)
|
JsValue::string(ptr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user