perf: ASCII 单字符缓存 + 字符串迭代器游标收尾

This commit is contained in:
2026-08-14 12:11:54 +08:00
parent 02dabbb845
commit e1747ca560
7 changed files with 229 additions and 25 deletions
+9 -8
View File
@@ -7,8 +7,6 @@ use oxide_runtime_api::{to_object, NativeResult, VmHost};
const INNER_PROP: &str = "__inner__";
const INDEX_PROP: &str = "__index__";
/// 字符串迭代的字节游标(`next` 增量推进的当前位置),避免每步整串复制 + 从头重扫。
const BYTEOFF_PROP: &str = "__byteoff__";
/// 占位构造函数:`Iterator` 不是构造函数,任何调用都抛 TypeError。
pub fn iterator_constructor<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
@@ -305,16 +303,19 @@ fn next_array_like<H: VmHost>(
}
if inner.is_string() {
let index = current_index(vm, wrapper, index_si);
let byteoff_si = vm.kernel_core().perm_interner().intern(BYTEOFF_PROP).0;
let byteoff = current_index(vm, wrapper, byteoff_si);
// index 槽在字符串分支存字节游标(每步推进一个 char 的 UTF-8 宽度)而非
// 元素序号:该槽只被 next 内部读写、无外部消费者,包装器创建后类型固定
// 不跨类型复用,故可安全借用语义(字符串=字节偏移)。
let byteoff = current_index(vm, wrapper, index_si);
// 源串裸指针借用压缩到单个表达式:ch 是 Copy 的 char,不携带借用,
// 之后对 VM 状态的可变访问不再与源串借用共存。
let ch = unsafe { &*inner.as_string_ptr() }.as_str()[byteoff..].chars().next();
if let Some(ch) = ch {
vm.set_or_create_prop_value(wrapper, byteoff_si, JsValue::int((byteoff + ch.len_utf8()) as i32));
vm.set_or_create_prop_value(wrapper, index_si, JsValue::int((index + 1) as i32));
let value = vm.new_string(&ch.to_string());
vm.set_or_create_prop_value(wrapper, index_si, JsValue::int((byteoff + ch.len_utf8()) as i32));
let value = match vm.single_char(ch) {
Some(v) => v,
None => vm.new_string(&ch.to_string()),
};
return Ok(Some(make_iter_result(vm, value, false)));
}
return Ok(Some(make_iter_result(vm, JsValue::undefined(), true)));
+45 -9
View File
@@ -201,6 +201,23 @@ pub(crate) fn make_string_array<H: VmHost>(vm: &mut H, parts: Vec<String>) -> Js
JsValue::from_js_object(arr)
}
/// 以已构造的字符串值构建字符串数组(元素零拷贝落地),供逐字符产出路径
/// (空分隔 split)复用,跳过 `Vec<String>` 中间层。
pub(crate) fn make_string_array_values<H: VmHost>(vm: &mut H, parts: Vec<JsValue>) -> JsValue {
let proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
let n = parts.len();
let arr =
vm.epoch()
.alloc(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(proto), n, vm.epoch().bump()));
unsafe {
for (i, sv) in parts.into_iter().enumerate() {
(*arr).set_prop_at(i, sv);
}
(*arr).set_prop_count(n);
}
JsValue::from_js_object(arr)
}
/// 取参数字符串内容:原始字符串零拷贝借用,其余经完整 ToString(对象 ToPrimitive)。
/// 返回借用绑定本次 `&mut` 借用;需与 `this_string` 借用并存时先 `into_owned` 落地。
fn as_string<'a, H: VmHost>(vm: &'a mut H, val: JsValue) -> Cow<'a, str> {
@@ -500,14 +517,24 @@ pub fn string_char_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if s.is_empty() {
return NativeResult::Ok(vm.new_string(""));
}
let first = take_chars(&s, 1);
return NativeResult::Ok(vm.new_string(&first));
let first = s.chars().next().unwrap();
// ASCII 走单字符缓存零分配,其余回落普通创建(借用已随 char 提取结束)。
return match vm.single_char(first) {
Some(v) => NativeResult::Ok(v),
None => NativeResult::Ok(vm.new_string(&first.to_string())),
};
}
if idx < 0 || idx as usize >= char_len(&s) {
return NativeResult::Ok(vm.new_string(""));
}
let ch = s.chars().nth(idx as usize).map(|c| c.to_string()).unwrap_or_default();
NativeResult::Ok(vm.new_string(&ch))
let ch = s.chars().nth(idx as usize);
match ch {
Some(c) => match vm.single_char(c) {
Some(v) => NativeResult::Ok(v),
None => NativeResult::Ok(vm.new_string(&c.to_string())),
},
None => NativeResult::Ok(vm.new_string("")),
}
}
/// `String.prototype.charCodeAt(index)`:返回指定位置字符的 UTF-16 code unit;越界返回 NaN。
@@ -950,11 +977,20 @@ pub fn string_split<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
}
// 无原生正则的类正则对象回退到字符串路径。
}
let parts: Vec<String> = if sep.is_empty() {
s.chars().map(|c| c.to_string()).take(limit).collect()
} else {
s.split(&sep).map(|p| p.to_string()).take(limit).collect()
};
if sep.is_empty() {
// 两段式:先借 this 收集字符(Copy),借用结束后再逐字符产出——
// ASCII 走单字符缓存零分配,其余回落普通创建。
let chars: Vec<char> = s.chars().take(limit).collect();
let mut values = Vec::with_capacity(chars.len());
for c in chars {
values.push(match vm.single_char(c) {
Some(v) => v,
None => vm.new_string(&c.to_string()),
});
}
return NativeResult::Ok(make_string_array_values(vm, values));
}
let parts: Vec<String> = s.split(&sep).map(|p| p.to_string()).take(limit).collect();
NativeResult::Ok(make_string_array(vm, parts))
}