perf: 数组整数键走独立 si 区间免字符串往返

This commit is contained in:
2026-08-13 17:47:55 +08:00
parent 1d1ee73743
commit 2c62830a1d
10 changed files with 199 additions and 60 deletions
+3 -3
View File
@@ -1,6 +1,6 @@
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::object::JsObject;
use oxide_types::private_key::make_well_known_symbol_key;
use oxide_types::private_key::{make_int_key, make_well_known_symbol_key};
use oxide_types::value::JsValue;
use oxide_runtime_api::{to_object, NativeResult, VmHost};
@@ -289,8 +289,8 @@ fn next_array_like<H: VmHost>(
let arr = unsafe { &*inner.as_js_object_ptr() };
if index < arr.prop_count() as usize {
// 数组元素读取走 GetValue:普通数据属性返回槽值,访问器属性
// (defineProperty getter)触发 getter 并透传异常。
let key_si = vm.kernel_core().perm_interner().intern(&index.to_string()).0;
// (defineProperty getter)触发 getter 并透传异常。整数键免 intern。
let key_si = make_int_key(index as u32);
let value = match vm.ordinary_get(arr, key_si, inner) {
Ok(v) => v,
Err(err) => {
+6 -9
View File
@@ -3,9 +3,10 @@ use std::fmt::Write;
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::object::JsObject;
use oxide_types::private_key::make_int_key;
use oxide_types::value::JsValue;
use crate::object::walk_own_keys;
use crate::object::{key_si_to_string, walk_own_keys};
use oxide_runtime_api::{NativeResult, VmHost};
@@ -74,8 +75,7 @@ fn walk_reviver<H: VmHost>(
if obj.is_array() {
let len = obj.prop_count() as usize;
for i in 0..len {
let index_str = i.to_string();
let child_si = vm.kernel_core().perm_interner().intern(&index_str).0;
let child_si = make_int_key(i as u32);
walk_reviver(vm, obj_ptr, child_si, reviver)?;
}
// 子节点可能已改写父级,重新读取当前值。
@@ -92,9 +92,8 @@ fn walk_reviver<H: VmHost>(
}
// 对当前值调用 reviver,用返回值覆盖属性槽。
let kc = vm.kernel_core().clone();
let key_str = kc.perm_interner().lookup(key_si).unwrap_or("");
let key_val = vm.new_string(key_str);
let key_str = key_si_to_string(vm, key_si);
let key_val = vm.new_string(&key_str);
let holder_val = JsValue::from_js_object(holder_ptr);
match vm.call_function_sync(reviver, holder_val, &[key_val, val]) {
Ok(new_val) => {
@@ -362,12 +361,10 @@ fn stringify_object<H: VmHost>(
let keys = walk_own_keys(vm, obj);
// 预计算 (名称, 槽位) 对,并应用白名单过滤。
let kc = vm.kernel_core().clone();
let perm_interner = kc.perm_interner();
let entries: Vec<(String, u32)> = keys
.into_iter()
.filter_map(|(si, pos)| {
let name = perm_interner.lookup(si).unwrap_or("").to_string();
let name = key_si_to_string(vm, si);
if let Some(whitelist) = replacer_whitelist {
if !whitelist.contains(&name) {
return None;
+34 -17
View File
@@ -2,7 +2,7 @@ use oxide_kernel::shape_forge::{ShapeForge, EMPTY_SHAPE_ID};
use oxide_kernel::string_forge::PermInterner;
use oxide_types::object::{JsObject, PropAttributes, PropMetaEntry};
use oxide_types::private_key::{
is_private_name_key, is_symbol_key, make_well_known_symbol_key, symbol_index_from_key,
int_key_value, is_int_key, is_private_name_key, is_symbol_key, make_well_known_symbol_key, symbol_index_from_key,
well_known_symbol_id_from_key,
};
use oxide_types::value::JsValue;
@@ -53,18 +53,37 @@ pub fn walk_own_keys<H: VmHost>(vm: &H, obj: &JsObject) -> Vec<(u32, u32)> {
pos += 1;
}
keys.sort_by(|(a_si, _), (b_si, _)| {
let a_str = vm.kernel_core().perm_interner().lookup(*a_si).unwrap_or("");
let b_str = vm.kernel_core().perm_interner().lookup(*b_si).unwrap_or("");
match (is_integer_index(a_str), is_integer_index(b_str)) {
(true, true) => a_str.parse::<u32>().unwrap().cmp(&b_str.parse::<u32>().unwrap()),
(true, false) => std::cmp::Ordering::Less,
(false, true) => std::cmp::Ordering::Greater,
(false, false) => std::cmp::Ordering::Equal,
// 整数键(含 INT 区间键)以数值升序排在普通字符串键之前。
let a_idx = int_or_string_index(vm, *a_si);
let b_idx = int_or_string_index(vm, *b_si);
match (a_idx, b_idx) {
(Some(ai), Some(bi)) => ai.cmp(&bi),
(Some(_), None) => std::cmp::Ordering::Less,
(None, Some(_)) => std::cmp::Ordering::Greater,
(None, None) => std::cmp::Ordering::Equal,
}
});
keys
}
/// 键 si 的数组下标值:整数键直接反解,字符串键查 interner 后判是否规范数字串。
fn int_or_string_index<H: VmHost>(vm: &H, si: u32) -> Option<u32> {
if is_int_key(si) {
return Some(int_key_value(si));
}
let key = vm.kernel_core().perm_interner().lookup(si)?;
is_integer_index(key).then(|| key.parse::<u32>().unwrap())
}
/// 键 si 物化为字符串文本:整数键反解数字串,其余查 interner。
pub fn key_si_to_string<H: VmHost>(vm: &H, si: u32) -> String {
if is_int_key(si) {
int_key_value(si).to_string()
} else {
vm.kernel_core().perm_interner().lookup(si).unwrap_or("").to_string()
}
}
/// 收集对象自身全部 Symbol 键(shape 链),按键序排列(根→叶,即插入序)。
/// 与 [`walk_own_keys`] 互补:只返回 Symbol 键,供 `getOwnPropertySymbols` 使用。
fn walk_own_symbol_keys<H: VmHost>(vm: &H, obj: &JsObject) -> Vec<(u32, u32)> {
@@ -156,6 +175,9 @@ pub fn object_get_own_property_symbols<H: VmHost>(vm: &mut H, args: &[u8]) -> Na
/// 字符串键是否为数组下标("0"~"4294967294",无前导零)。
fn array_index_of<H: VmHost>(vm: &H, key_si: u32) -> Option<u32> {
if is_int_key(key_si) {
return Some(int_key_value(key_si));
}
let key = vm.kernel_core().perm_interner().lookup(key_si)?;
if key.is_empty() || (key.len() > 1 && key.starts_with('0')) {
return None;
@@ -356,10 +378,7 @@ pub fn object_keys<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
.unwrap_or(PropAttributes::DEFAULT_DATA.enumerable())
})
.collect();
key_names = owned_keys
.iter()
.map(|(si, _offset)| vm.kernel_core().perm_interner().lookup(*si).unwrap_or("").to_string())
.collect();
key_names = owned_keys.iter().map(|(si, _offset)| key_si_to_string(vm, *si)).collect();
}
let n = key_names.len();
let array_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
@@ -883,9 +902,7 @@ pub fn object_get_own_property_names<H: VmHost>(vm: &mut H, args: &[u8]) -> Nati
let key_names: Vec<String> = {
let obj = unsafe { &*obj_ptr };
let keys = walk_own_keys(vm, obj);
keys.iter()
.map(|(si, _)| vm.kernel_core().perm_interner().lookup(*si).unwrap_or("").to_string())
.collect()
keys.iter().map(|(si, _)| key_si_to_string(vm, *si)).collect()
};
let n = key_names.len();
@@ -1128,8 +1145,8 @@ pub fn object_entries<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
vm.epoch().bump(),
));
for (i, (si, offset)) in owned_keys.iter().enumerate() {
let key_str = vm.kernel_core().perm_interner().lookup(*si).unwrap_or_default();
let key_val = vm.new_string(key_str);
let key_str = key_si_to_string(vm, *si);
let key_val = vm.new_string(&key_str);
let val = obj.get_prop_at(*offset);
let pair = vm.alloc_object(JsObject::new_array(
EMPTY_SHAPE_ID,
+2 -2
View File
@@ -3,7 +3,7 @@ use oxide_types::mem::P;
use oxide_types::object::{JsObject, PropAttributes};
use oxide_types::value::JsValue;
use crate::object::{delete_own_property, walk_own_keys};
use crate::object::{delete_own_property, key_si_to_string, walk_own_keys};
use oxide_runtime_api::{NativeResult, VmHost};
@@ -193,7 +193,7 @@ pub fn reflect_own_keys<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let target = unsafe { &*target_ptr };
let key_names: Vec<String> = walk_own_keys(vm, target)
.into_iter()
.map(|(si, _)| vm.kernel_core().perm_interner().lookup(si).unwrap_or("").to_string())
.map(|(si, _)| key_si_to_string(vm, si))
.collect();
NativeResult::Ok(make_string_array(vm, &key_names))
}
+10 -5
View File
@@ -1,5 +1,6 @@
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::object::{JsObject, NativeFnPtr, TypedArrayKind};
use oxide_types::private_key::{int_key_value, is_int_key};
use oxide_types::value::JsValue;
use crate::array_buffer::{array_buffer_data_ptr, new_array_buffer, MAX_ARRAY_BUFFER_LENGTH};
@@ -214,11 +215,15 @@ pub fn typed_array_integer_index<H: VmHost>(vm: &mut H, obj: &JsObject, prop_nam
return None;
}
let view = unsafe { *ptr };
let key = vm.kernel_core().perm_interner().lookup(prop_name_si)?;
if key.is_empty() || (key.len() > 1 && key.starts_with('0')) {
return None;
}
let index = key.parse::<u32>().ok()?;
let index = if is_int_key(prop_name_si) {
int_key_value(prop_name_si)
} else {
let key = vm.kernel_core().perm_interner().lookup(prop_name_si)?;
if key.is_empty() || (key.len() > 1 && key.starts_with('0')) {
return None;
}
key.parse::<u32>().ok()?
};
Some((index as usize, view.length))
}